· 8 years ago · Jul 08, 2018, 08:16 PM
1<html dir="ltr" lang="pt" i18n-processed=""><head>
2 <meta charset="utf-8">
3 <meta name="viewport" content="width=device-width, initial-scale=1.0,
4 maximum-scale=1.0, user-scalable=no">
5 <title>404</title>
6 <style>/* Copyright 2017 The Chromium Authors. All rights reserved.
7 * Use of this source code is governed by a BSD-style license that can be
8 * found in the LICENSE file. */
9
10a {
11 color: rgb(88, 88, 88);
12}
13
14body {
15 background-color: rgb(247, 247, 247);
16 color: rgb(100, 100, 100);
17 word-wrap: break-word;
18}
19
20#details-button {
21 background: inherit;
22 border: 0;
23 float: none;
24 margin: 0;
25 padding: 10px 0;
26 text-transform: uppercase;
27}
28
29.hidden {
30 display: none;
31}
32
33html {
34 -webkit-text-size-adjust: 100%;
35 font-size: 125%;
36}
37
38.icon {
39 background-repeat: no-repeat;
40 background-size: 100%;
41}</style>
42 <style>/* Copyright 2014 The Chromium Authors. All rights reserved.
43 Use of this source code is governed by a BSD-style license that can be
44 found in the LICENSE file. */
45
46button {
47 border: 0;
48 border-radius: 2px;
49 box-sizing: border-box;
50 color: #fff;
51 cursor: pointer;
52 float: right;
53 font-size: .875em;
54 margin: 0;
55 padding: 10px 24px;
56 transition: box-shadow 200ms cubic-bezier(0.4, 0, 0.2, 1);
57 user-select: none;
58}
59
60[dir='rtl'] button {
61 float: left;
62}
63
64.bad-clock button,
65.captive-portal button,
66.main-frame-blocked button,
67.neterror button,
68.offline button,
69.pdf button,
70.ssl button {
71 background: rgb(66, 133, 244);
72}
73
74button:active {
75 background: rgb(50, 102, 213);
76 outline: 0;
77}
78
79button:hover {
80 box-shadow: 0 1px 3px rgba(0, 0, 0, .50);
81}
82
83#debugging {
84 display: inline;
85 overflow: auto;
86}
87
88.debugging-content {
89 line-height: 1em;
90 margin-bottom: 0;
91 margin-top: 1em;
92}
93
94.debugging-content-fixed-width {
95 display: block;
96 font-family: monospace;
97 font-size: 1.2em;
98 margin-top: 0.5em;
99}
100
101.debugging-title {
102 font-weight: bold;
103}
104
105#details {
106 color: #696969;
107 margin: 0 0 50px;
108}
109
110#details p:not(:first-of-type) {
111 margin-top: 20px;
112}
113
114#details-button:hover {
115 box-shadow: inherit;
116 text-decoration: underline;
117}
118
119.error-code {
120 color: #646464;
121 font-size: .86667em;
122 text-transform: uppercase;
123}
124
125#error-debugging-info {
126 font-size: 0.8em;
127}
128
129h1 {
130 color: #333;
131 font-size: 1.6em;
132 font-weight: normal;
133 line-height: 1.25em;
134 margin-bottom: 16px;
135}
136
137h2 {
138 font-size: 1.2em;
139 font-weight: normal;
140}
141
142.icon {
143 height: 72px;
144 margin: 0 0 40px;
145 width: 72px;
146}
147
148input[type=checkbox] {
149 opacity: 0;
150}
151
152input[type=checkbox]:focus ~ .checkbox {
153 outline: -webkit-focus-ring-color auto 5px;
154}
155
156.interstitial-wrapper {
157 box-sizing: border-box;
158 font-size: 1em;
159 line-height: 1.6em;
160 margin: 14vh auto 0;
161 max-width: 600px;
162 width: 100%;
163}
164
165#main-message > p {
166 display: inline;
167}
168
169#extended-reporting-opt-in {
170 font-size: .875em;
171 margin-top: 39px;
172}
173
174#extended-reporting-opt-in label {
175 position: relative;
176 display: flex;
177 align-items: flex-start;
178}
179
180.nav-wrapper {
181 margin-top: 51px;
182}
183
184.nav-wrapper::after {
185 clear: both;
186 content: '';
187 display: table;
188 width: 100%;
189}
190
191.small-link {
192 color: #696969;
193 font-size: .875em;
194}
195
196.checkboxes {
197 flex: 0 0 24px;
198}
199
200.checkbox {
201 background: transparent;
202 border: 1px solid white;
203 border-radius: 2px;
204 display: block;
205 height: 14px;
206 left: 0;
207 position: absolute;
208 right: 0;
209 top: 3px;
210 width: 14px;
211}
212
213.checkbox::before {
214 background: transparent;
215 border: 2px solid white;
216 border-right-width: 0;
217 border-top-width: 0;
218 content: '';
219 height: 4px;
220 left: 2px;
221 opacity: 0;
222 position: absolute;
223 top: 3px;
224 transform: rotate(-45deg);
225 width: 9px;
226}
227
228input[type=checkbox]:checked ~ .checkbox::before {
229 opacity: 1;
230}
231
232@media (max-width: 700px) {
233 .interstitial-wrapper {
234 padding: 0 10%;
235 }
236
237 #error-debugging-info {
238 overflow: auto;
239 }
240}
241
242@media (max-height: 600px) {
243 .error-code {
244 margin-top: 10px;
245 }
246}
247
248@media (max-width: 420px) {
249 button,
250 [dir='rtl'] button,
251 .small-link {
252 float: none;
253 font-size: .825em;
254 font-weight: 400;
255 margin: 0;
256 text-transform: uppercase;
257 width: 100%;
258 }
259
260 #details {
261 margin: 20px 0 20px 0;
262 }
263
264 #details p:not(:first-of-type) {
265 margin-top: 10px;
266 }
267
268 #details-button {
269 display: block;
270 margin-top: 20px;
271 text-align: center;
272 width: 100%;
273 }
274
275 .interstitial-wrapper {
276 padding: 0 5%;
277 }
278
279 #extended-reporting-opt-in {
280 margin-top: 24px;
281 }
282
283 .nav-wrapper {
284 margin-top: 30px;
285 }
286}
287
288/**
289 * Mobile specific styling.
290 * Navigation buttons are anchored to the bottom of the screen.
291 * Details message replaces the top content in its own scrollable area.
292 */
293
294@media (max-width: 420px) {
295 #details-button {
296 border: 0;
297 margin: 8px 0 0;
298 }
299
300 .secondary-button {
301 -webkit-margin-end: 0;
302 margin-top: 16px;
303 }
304}
305
306/* Fixed nav. */
307@media (min-width: 240px) and (max-width: 420px) and
308 (min-height: 401px),
309 (min-width: 421px) and (min-height: 240px) and
310 (max-height: 560px) {
311 body .nav-wrapper {
312 background: #f7f7f7;
313 bottom: 0;
314 box-shadow: 0 -22px 40px rgb(247, 247, 247);
315 left: 0;
316 margin: 0 auto;
317 max-width: 736px;
318 padding-left: 24px;
319 padding-right: 24px;
320 position: fixed;
321 right: 0;
322 width: 100%;
323 z-index: 2;
324 }
325
326 .interstitial-wrapper {
327 max-width: 736px;
328 }
329
330 #details,
331 #main-content {
332 padding-bottom: 40px;
333 }
334
335 #details {
336 padding-top: 5.5vh;
337 }
338
339 #details-button:hover {
340 box-shadow: none;
341 }
342}
343
344@media (max-width: 420px) and (orientation: portrait),
345 (max-height: 560px) {
346 body {
347 margin: 0 auto;
348 }
349
350 button,
351 [dir='rtl'] button,
352 button.small-link {
353 font-family: Roboto-Regular,Helvetica;
354 font-size: .933em;
355 font-weight: 600;
356 margin: 6px 0;
357 text-transform: uppercase;
358 transform: translatez(0);
359 }
360
361 .nav-wrapper {
362 box-sizing: border-box;
363 padding-bottom: 8px;
364 width: 100%;
365 }
366
367 .error-code {
368 margin-top: 0;
369 }
370
371 #details {
372 box-sizing: border-box;
373 height: auto;
374 margin: 0;
375 opacity: 1;
376 transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1);
377 }
378
379 #details.hidden,
380 #main-content.hidden {
381 display: block;
382 height: 0;
383 opacity: 0;
384 overflow: hidden;
385 padding-bottom: 0;
386 transition: none;
387 }
388
389 #details-button {
390 padding-bottom: 16px;
391 padding-top: 16px;
392 }
393
394 h1 {
395 font-size: 1.5em;
396 margin-bottom: 8px;
397 }
398
399 .icon {
400 margin-bottom: 5.69vh;
401 }
402
403 .interstitial-wrapper {
404 box-sizing: border-box;
405 margin: 7vh auto 12px;
406 padding: 0 24px;
407 position: relative;
408 }
409
410 .interstitial-wrapper p {
411 font-size: .95em;
412 line-height: 1.61em;
413 margin-top: 8px;
414 }
415
416 #main-content {
417 margin: 0;
418 transition: opacity 100ms cubic-bezier(0.4, 0, 0.2, 1);
419 }
420
421 .small-link {
422 border: 0;
423 }
424
425 .suggested-left > #control-buttons,
426 .suggested-right > #control-buttons {
427 float: none;
428 margin: 0;
429 }
430}
431
432@media (min-width: 421px) and (min-height: 500px) and (max-height: 560px) {
433 .interstitial-wrapper {
434 margin-top: 10vh;
435 }
436}
437
438@media (min-height: 400px) and (orientation:portrait) {
439 .interstitial-wrapper {
440 margin-bottom: 145px;
441 }
442}
443
444@media (min-height: 299px) {
445 .nav-wrapper {
446 padding-bottom: 16px;
447 }
448}
449
450@media (min-height: 500px) and (max-height: 650px) and (max-width: 414px) and
451 (orientation: portrait) {
452 .interstitial-wrapper {
453 margin-top: 7vh;
454 }
455}
456
457@media (min-height: 650px) and (max-width: 414px) and (orientation: portrait) {
458 .interstitial-wrapper {
459 margin-top: 10vh;
460 }
461}
462
463/* Small mobile screens. No fixed nav. */
464@media (max-height: 400px) and (orientation: portrait),
465 (max-height: 239px) and (orientation: landscape),
466 (max-width: 419px) and (max-height: 399px) {
467 .interstitial-wrapper {
468 display: flex;
469 flex-direction: column;
470 margin-bottom: 0;
471 }
472
473 #details {
474 flex: 1 1 auto;
475 order: 0;
476 }
477
478 #main-content {
479 flex: 1 1 auto;
480 order: 0;
481 }
482
483 .nav-wrapper {
484 flex: 0 1 auto;
485 margin-top: 8px;
486 order: 1;
487 padding-left: 0;
488 padding-right: 0;
489 position: relative;
490 width: 100%;
491 }
492}
493
494@media (max-width: 239px) and (orientation: portrait) {
495 .nav-wrapper {
496 padding-left: 0;
497 padding-right: 0;
498 }
499}
500</style>
501 <style>/* Copyright 2013 The Chromium Authors. All rights reserved.
502 * Use of this source code is governed by a BSD-style license that can be
503 * found in the LICENSE file. */
504
505/* Don't use the main frame div when the error is in a subframe. */
506html[subframe] #main-frame-error {
507 display: none;
508}
509
510/* Don't use the subframe error div when the error is in a main frame. */
511html:not([subframe]) #sub-frame-error {
512 display: none;
513}
514
515#diagnose-button {
516 -webkit-margin-start: 0;
517 float: none;
518 margin-bottom: 10px;
519 margin-top: 20px;
520}
521
522h1 {
523 margin-top: 0;
524 word-wrap: break-word;
525}
526
527h1 span {
528 font-weight: 500;
529}
530
531h2 {
532 color: #666;
533 font-size: 1.2em;
534 font-weight: normal;
535 margin: 10px 0;
536}
537
538a {
539 color: rgb(17, 85, 204);
540 text-decoration: none;
541}
542
543.icon {
544 -webkit-user-select: none;
545 display: inline-block;
546}
547
548.icon-generic {
549 /**
550 * Can't access chrome://theme/IDR_ERROR_NETWORK_GENERIC from an untrusted
551 * renderer process, so embed the resource manually.
552 */
553 content: -webkit-image-set(
554 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAQMAAABvIyEEAAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAENJREFUeF7tzbEJACEQRNGBLeAasBCza2lLEGx0CxFGG9hBMDDxRy/72O9FMnIFapGylsu1fgoBdkXfUHLrQgdfrlJN1BdYBjQQm3UAAAAASUVORK5CYII=) 1x,
555 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQAQMAAADdiHD7AAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAFJJREFUeF7t0cENgDAMQ9FwYgxG6WjpaIzCCAxQxVggFuDiCvlLOeRdHR9yzjncHVoq3npu+wQUrUuJHylSTmBaespJyJQoObUeyxDQb3bEm5Au81c0pSCD8HYAAAAASUVORK5CYII=) 2x);
556}
557
558.icon-offline {
559 content: -webkit-image-set(
560 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAQMAAABvIyEEAAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAGxJREFUeF7tyMEJwkAQRuFf5ipMKxYQiJ3Z2nSwrWwBA0+DQZcdxEOueaePp9+dQZFB7GpUcURSVU66yVNFj6LFICatThZB6r/ko/pbRpUgilY0Cbw5sNmb9txGXUKyuH7eV25x39DtJXUNPQGJtWFV+BT/QAAAAABJRU5ErkJggg==) 1x,
561 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQBAMAAAAVaP+LAAAAGFBMVEUAAABTU1NNTU1TU1NPT09SUlJSUlJTU1O8B7DEAAAAB3RSTlMAoArVKvVgBuEdKgAAAJ1JREFUeF7t1TEOwyAMQNG0Q6/UE+RMXD9d/tC6womIFSL9P+MnAYOXeTIzMzMzMzMzaz8J9Ri6HoITmuHXhISE8nEh9yxDh55aCEUoTGbbQwjqHwIkRAEiIaG0+0AA9VBMaE89Rogeoww936MQrWdBr4GN/z0IAdQ6nQ/FIpRXDwHcA+JIJcQowQAlFUA0MfQpXLlVQfkzR4igS6ENjknm/wiaGhsAAAAASUVORK5CYII=) 2x);
562 position: relative;
563}
564
565.icon-disabled {
566 content: -webkit-image-set(
567 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHAAAABICAMAAAAZF4G5AAAABlBMVEVMaXFTU1OXUj8tAAAAAXRSTlMAQObYZgAAASZJREFUeAHd11Fq7jAMRGGf/W/6PoWB67YMqv5DybwG/CFjRuR8JBw3+ByiRjgV9W/TJ31P0tBfC6+cj1haUFXKHmVJo5wP98WwQ0ZCbfUc6LQ6VuUBz31ikADkLMkDrfUC4rR6QGW+gF6rx7NaHWCj1Y/W6lf4L7utvgBSt3rBFSS/XBMPUILcJINHCBWYUfpWn4NBi1ZfudIc3rf6/NGEvEA+AsYTJozmXemjXeLZAov+mnkN2HfzXpMSVQDnGw++57qNJ4D1xitA2sJ+VAWMygSEaYf2mYPTjZfk2K8wmP7HLIH5Mg4/pP+PEcDzUvDMvYbs/2NWwPO5vBdMZE4EE5UTQLiBFDaUlTDPBRoJ9HdAYIkIo06og3BNXtCzy7zA1aXk5x+tJARq63eAygAAAABJRU5ErkJggg==) 1x,
568 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOAAAACQAQMAAAArwfVjAAAABlBMVEVMaXFTU1OXUj8tAAAAAXRSTlMAQObYZgAAAYdJREFUeF7F1EFqwzAUBNARAmVj0FZe5QoBH6BX+dn4GlY2PYNzGx/A0CvkCIJuvIraKJKbgBvzf2g62weDGD7CYggpfFReis4J0ey9EGFIiEQQojFSlA9kSIiqd0KkFjKsewgRbStEN19mxUPTtmW9HQ/h6tyqNQ8NlSMZdzyE6qkoE0trVYGFm0n1WYeBhduzwbwBC7voS+vIxfeMjeaiLxsMMtQNwMPtuew+DjzcTHk8YMfDknEcIUOtf2lVfgVH3K4Xv5PRYAXRVMtItIJ3rfaCIVn9DsTH2NxisAVRex2Hh3hX+/mRUR08bAwPEYsI51ZxWH4Q0SpicQRXeyEaIug48FEdegARfMz/tADVsRciwTAxW308ehmC2gLraC+YCbV3QoTZexa+zegAEW5PhhgYfmbvJgcRqngGByOSXdFJcLk2JeDPEN0kxe1JhIt5FiFA+w+ItMELsUyPF2IaJ4aILqb4FbxPwhImwj6JauKgDUCYaxmYIsd4KXdMjIC9ItB5Bn4BNRwsG0XM2nwAAAAASUVORK5CYII=) 2x);
569 width: 112px;
570}
571
572.error-code {
573 display: block;
574 font-size: .8em;
575}
576
577#content-top {
578 margin: 20px;
579}
580
581#help-box-inner {
582 background-color: #f9f9f9;
583 border-top: 1px solid #EEE;
584 color: #444;
585 padding: 20px;
586 text-align: start;
587}
588
589.hidden {
590 display: none;
591}
592
593#suggestion {
594 margin-top: 15px;
595}
596
597#suggestions-list p {
598 -webkit-margin-after: 0;
599}
600
601#suggestions-list ul {
602 margin-top: 0;
603}
604
605.single-suggestion {
606 list-style-type: none;
607 padding-left: 0;
608}
609
610#short-suggestion {
611 margin-top: 5px;
612}
613
614#sub-frame-error-details {
615
616 color: #8F8F8F;
617
618 /* Not done on mobile for performance reasons. */
619 text-shadow: 0 1px 0 rgba(255,255,255,0.3);
620
621}
622
623[jscontent=hostName],
624[jscontent=failedUrl] {
625 overflow-wrap: break-word;
626}
627
628#search-container {
629 /* Prevents a space between controls. */
630 display: flex;
631 margin-top: 20px;
632}
633
634#search-box {
635 border: 1px solid #cdcdcd;
636 flex-grow: 1;
637 font-size: 1em;
638 height: 26px;
639 margin-right: 0;
640 padding: 1px 9px;
641}
642
643#search-box:focus {
644 border: 1px solid rgb(93, 154, 255);
645 outline: none;
646}
647
648#search-button {
649 border: none;
650 border-bottom-left-radius: 0;
651 border-top-left-radius: 0;
652 box-shadow: none;
653 display: flex;
654 height: 30px;
655 margin: 0;
656 padding: 0;
657 width: 60px;
658}
659
660#search-image {
661 content:
662 -webkit-image-set(
663 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAPCAQAAAB+HTb/AAAArElEQVR4Xn3NsUoCUBzG0XvB3U0chR4geo5qihpt6gkCx0bXFsMERWj2KWqIanAvmlUUoQapwU6g4l8H5bd9Z/iSPS0hu/RqZqrncBuzLl7U3Rn4cSpQFTeroejJl1Lgs7f4ceDPdeBMXYp86gaONYJkY83AnqHiGk9wHnjk16PKgo5N9BUCkzPf5j6M0PfuVg5MymoetFwoaKAlB26WdXAvJ7u5mezitqtkT//7Sv/u96CaLQAAAABJRU5ErkJggg==) 1x,
664 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAeCAQAAACVzLYUAAABYElEQVR4Xr3VMUuVURzH8XO98jgkGikENkRD0KRGDUVDQy0h2SiC4IuIiktL4AvQt1CDBJUJwo1KXXS6cWdHw7tcjWwoC5Hrx+UZgnNO5CXiO/75jD/+QZf9MzjskVU7DrU1zRv9G9ir5hsA4Nii83+GA9ZI1nI1D6tWAE1TRlQMuuuFDthzMQefgo4nKr+f3dIGDdUUHPYD1ISoMQdgJgUfgqaKEOcxWE/BVTArJBvwC0cGY7gNLgiZNsD1GP4EPVn4EtyLYRuczcJ34HYMP4E7GdajDS7FcB48z8AJ8FmI4TjouBkzZ2yBuRQMlsButIZ+dfDVUBqOaIHvavpLVHXfFmAqv45r9gEHNr3y3hcAfLSgSMPgiiZR+6Z9AMuKNAwqpjUcA2h55pxgAfBWkYRlQ254YMJloaxPHbCkiGCymL5RlLA7GnRDXyuC7uhicLoKdRyaDE5Pl00K//93nABqPgBDK8sfWgAAAABJRU5ErkJggg==) 2x);
665 margin: auto;
666}
667
668.secondary-button {
669 -webkit-margin-end: 16px;
670 background: #d9d9d9;
671 color: #696969;
672}
673
674.snackbar {
675 background: #323232;
676 border-radius: 2px;
677 bottom: 24px;
678 box-sizing: border-box;
679 color: #fff;
680 font-size: .87em;
681 left: 24px;
682 max-width: 568px;
683 min-width: 288px;
684 opacity: 0;
685 padding: 16px 24px 12px;
686 position: fixed;
687 transform: translateY(90px);
688 will-change: opacity, transform;
689 z-index: 999;
690}
691
692.snackbar-show {
693 -webkit-animation:
694 show-snackbar .25s cubic-bezier(0.0, 0.0, 0.2, 1) forwards,
695 hide-snackbar .25s cubic-bezier(0.4, 0.0, 1, 1) forwards 5s;
696}
697
698@-webkit-keyframes show-snackbar {
699 100% {
700 opacity: 1;
701 transform: translateY(0);
702 }
703}
704
705@-webkit-keyframes hide-snackbar {
706 0% {
707 opacity: 1;
708 transform: translateY(0);
709 }
710 100% {
711 opacity: 0;
712 transform: translateY(90px);
713 }
714}
715
716.suggestions {
717 margin-top: 18px;
718}
719
720.suggestion-header {
721 font-weight: bold;
722 margin-bottom: 4px;
723}
724
725.suggestion-body {
726 color: #777;
727}
728
729/* Increase line height at higher resolutions. */
730@media (min-width: 641px) and (min-height: 641px) {
731 #help-box-inner {
732 line-height: 18px;
733 }
734}
735
736/* Decrease padding at low sizes. */
737@media (max-width: 640px), (max-height: 640px) {
738 h1 {
739 margin: 0 0 15px;
740 }
741 #content-top {
742 margin: 15px;
743 }
744 #help-box-inner {
745 padding: 20px;
746 }
747 .suggestions {
748 margin-top: 10px;
749 }
750 .suggestion-header {
751 margin-bottom: 0;
752 }
753}
754
755/* Don't allow overflow when in a subframe. */
756html[subframe] body {
757 overflow: hidden;
758}
759
760#sub-frame-error {
761 -webkit-align-items: center;
762 background-color: #DDD;
763 display: -webkit-flex;
764 -webkit-flex-flow: column;
765 height: 100%;
766 -webkit-justify-content: center;
767 left: 0;
768 position: absolute;
769 text-align: center;
770 top: 0;
771 transition: background-color .2s ease-in-out;
772 width: 100%;
773}
774
775#sub-frame-error:hover {
776 background-color: #EEE;
777}
778
779#sub-frame-error .icon-generic {
780 margin: 0 0 16px;
781}
782
783#sub-frame-error-details {
784 margin: 0 10px;
785 text-align: center;
786 visibility: hidden;
787}
788
789/* Show details only when hovering. */
790#sub-frame-error:hover #sub-frame-error-details {
791 visibility: visible;
792}
793
794/* If the iframe is too small, always hide the error code. */
795/* TODO(mmenke): See if overflow: no-display works better, once supported. */
796@media (max-width: 200px), (max-height: 95px) {
797 #sub-frame-error-details {
798 display: none;
799 }
800}
801
802/* Adjust icon for small embedded frames in apps. */
803@media (max-height: 100px) {
804 #sub-frame-error .icon-generic {
805 height: auto;
806 margin: 0;
807 padding-top: 0;
808 width: 25px;
809 }
810}
811
812/* details-button is special; it's a <button> element that looks like a link. */
813#details-button {
814 box-shadow: none;
815 min-width: 0;
816}
817
818/* Styles for platform dependent separation of controls and details button. */
819.suggested-left > #control-buttons,
820.suggested-left #stale-load-button,
821.suggested-right > #details-button {
822 float: left;
823}
824
825.suggested-right > #control-buttons,
826.suggested-right #stale-load-button,
827.suggested-left > #details-button {
828 float: right;
829}
830
831.suggested-left .secondary-button {
832 -webkit-margin-end: 0px;
833 -webkit-margin-start: 16px;
834}
835
836#details-button.singular {
837 float: none;
838}
839
840/* download-button shows both icon and text. */
841#download-button {
842 height: 48px;
843}
844
845.download-button {
846 box-shadow: none;
847 position: relative;
848}
849
850.download-button:before {
851 -webkit-margin-end: 4px;
852 background: -webkit-image-set(
853 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAO0lEQVQ4y2NgGArgPxIY1YChsOE/LtBAmpYG0mxpIOSDBpKUo2lpIDZxNJCkHKqlYZAla3RAHQ1DFgAARRroHyLNTwwAAAAASUVORK5CYII=) 1x,
854 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAQAAAD9CzEMAAAAZElEQVRYw+3Ruw3AMAwDUY3OzZUmRRD4E9iim9wNwAdbEURHyk4AAAAATiCVK8lLyPsKeT9K3lsownnunfkPxO78hKiYHxBV8x2icr5BVM+/CMf8g3DN34Rzns6ViwHUAUQ/6wIAd5Km7l6c8AAAAABJRU5ErkJggg==) 2x)
855 no-repeat;
856 content: '';
857 display: inline-block;
858 width: 24px;
859 height: 24px;
860 vertical-align: middle;
861}
862
863.download-button:disabled {
864 background: rgb(180, 206, 249);
865 color: rgb(255, 255, 255);
866}
867
868/* Alternate dino page button styles */
869#control-buttons .reload-button-alternate:disabled {
870 background: #ccc;
871 color: #fff;
872 font-size: 14px;
873 height: 48px;
874}
875
876/* Overriding default control button styles */
877#control-buttons .download-button-alternate,
878#control-buttons .download-button-alternate-disabled {
879 background: none;
880 box-shadow: none;
881}
882
883#control-buttons .download-button-alternate:active {
884 background: rgba(66, 133, 244, 0.12);
885}
886
887.download-button-alternate {
888 color: rgb(66, 133, 244);
889}
890
891.download-button-alternate:before {
892 -webkit-margin-end: 4px;
893 background: -webkit-image-set(
894 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gQLABA2MKG2IAAAAG1JREFUSMdjZEhLY6AlYGKgMaC5BSzEKHKS7/uPTXzfwyLG4R9EoxYQBIzYMhquVEMIYEtVTOQmP2L1MJGqgVS1TORqJFYNEyUGUC0nYzOI2CBkIiecSYkfxiFfH7CQUjxTLaPRvKgYLU3pagEAMvYoORANnUkAAAAASUVORK5CYII=) 1x,
895 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gQLABIjb0owSQAAAK5JREFUaN7tl8EJhUAMBdWzVdmWZ9uyqr1rAyKb5GUlOHP++DOIMG+aAAC+ZFY9aDvaZfn9ua+S/16qvwEEEEAAAQQQQKBEC1lbJ0pvK5mCapSEJfTMRZgtYa1UV9JmSXgS293kagnvPgiNCpVEZNyEV1FUIrrMJLPOK6GYleU38az8EHslVMfLBXoklMenCLxJqI9PE3iSyDg+ne1o1+gIBPgb5RcZox4BAACAADdDs0GgpWiAqQAAAABJRU5ErkJggg==) 2x)
896 no-repeat;
897 content: '';
898 display: inline-block;
899 width: 24px;
900 height: 24px;
901 vertical-align: middle;
902}
903
904.download-button-alternate-disabled {
905 color: rgba(0, 0, 0, 0.38);
906}
907
908.download-button-alternate-disabled:before {
909 background: -webkit-image-set(
910 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfiBAwFEx/CIofdAAAAU0lEQVQ4y2NgGHSAEVNo3n8EOwlDnolUG4aDBkbsoYMOEKHFiCtAsSvHiAdsWlDjghFftGGLOpJjmhGbm2FakhiJDrx5//GFGd2SN3ERx8QwAgEATUETT/jYf10AAAAASUVORK5CYII=) 1x,
911 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAQAAAD9CzEMAAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfiBAwFEyh6nyLSAAAAj0lEQVRYw+2WQQrAIBADTZ+6D/Or7ak36yZrhVIyR1EHERNbM2YVMJP6OR4PYvWx+wQWWGDBHwRQsidjlE3Q4k3dfpKmquIpWaGHtBbcqPSA0guoVQ1fO2knZYqs1YjSmyny0tzeyeCucaQIlF8yowgsREWuCHodlNd6KwJtF/2sBqH52O/6zUbzr8IYQ3AB29ghZu2kC30AAAAASUVORK5CYII=) 2x)
912 no-repeat;
913}
914
915#buttons::after {
916 clear: both;
917 content: '';
918 display: block;
919 width: 100%;
920}
921
922/* Offline page */
923.offline {
924 transition: -webkit-filter 1.5s cubic-bezier(0.65, 0.05, 0.36, 1),
925 background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1);
926 will-change: -webkit-filter, background-color;
927}
928
929.offline #main-message > p {
930 display: none;
931}
932
933.offline.inverted {
934 -webkit-filter: invert(100%);
935 background-color: #000;
936}
937
938.offline .interstitial-wrapper {
939 color: #2b2b2b;
940 font-size: 1em;
941 line-height: 1.55;
942 margin: 0 auto;
943 max-width: 600px;
944 padding-top: 100px;
945 width: 100%;
946}
947
948.offline .runner-container {
949 direction: ltr;
950 height: 150px;
951 max-width: 600px;
952 overflow: hidden;
953 position: absolute;
954 top: 35px;
955 width: 44px;
956}
957
958.offline .runner-canvas {
959 height: 150px;
960 max-width: 600px;
961 opacity: 1;
962 overflow: hidden;
963 position: absolute;
964 top: 0;
965 z-index: 2;
966}
967
968.offline .controller {
969 background: rgba(247,247,247, .1);
970 height: 100vh;
971 left: 0;
972 position: absolute;
973 top: 0;
974 width: 100vw;
975 z-index: 1;
976}
977
978#offline-resources {
979 display: none;
980}
981
982@media (max-width: 420px) {
983 .suggested-left > #control-buttons,
984 .suggested-right > #control-buttons {
985 float: none;
986 }
987
988 .snackbar {
989 left: 0;
990 bottom: 0;
991 width: 100%;
992 border-radius: 0;
993 }
994}
995
996@media (max-height: 350px) {
997 h1 {
998 margin: 0 0 15px;
999 }
1000
1001 .icon-offline {
1002 margin: 0 0 10px;
1003 }
1004
1005 .interstitial-wrapper {
1006 margin-top: 5%;
1007 }
1008
1009 .nav-wrapper {
1010 margin-top: 30px;
1011 }
1012}
1013
1014@media (min-width: 420px) and (max-width: 736px) and
1015 (min-height: 240px) and (max-height: 420px) and
1016 (orientation:landscape) {
1017 .interstitial-wrapper {
1018 margin-bottom: 100px;
1019 }
1020}
1021
1022@media (max-width: 360px) and (max-height: 480px) {
1023 .offline .interstitial-wrapper {
1024 padding-top: 60px;
1025 }
1026
1027 .offline .runner-container {
1028 top: 8px;
1029 }
1030}
1031
1032@media (min-height: 240px) and (orientation: landscape) {
1033 .offline .interstitial-wrapper {
1034 margin-bottom: 90px;
1035 }
1036
1037 .icon-offline {
1038 margin-bottom: 20px;
1039 }
1040}
1041
1042@media (max-height: 320px) and (orientation: landscape) {
1043 .icon-offline {
1044 margin-bottom: 0;
1045 }
1046
1047 .offline .runner-container {
1048 top: 10px;
1049 }
1050}
1051
1052@media (max-width: 240px) {
1053 button {
1054 padding-left: 12px;
1055 padding-right: 12px;
1056 }
1057
1058 .interstitial-wrapper {
1059 overflow: inherit;
1060 padding: 0 8px;
1061 }
1062}
1063
1064@media (max-width: 120px) {
1065 button {
1066 width: auto;
1067 }
1068}
1069
1070.arcade-mode,
1071.arcade-mode .runner-container,
1072.arcade-mode .runner-canvas {
1073 image-rendering: pixelated;
1074 max-width: 100%;
1075 overflow: hidden;
1076}
1077
1078.arcade-mode #buttons,
1079.arcade-mode #main-content {
1080 opacity: 0;
1081 overflow: hidden;
1082}
1083
1084.arcade-mode .interstitial-wrapper {
1085 height: 100vh;
1086 max-width: 100%;
1087 overflow: hidden;
1088}
1089
1090.arcade-mode .runner-container {
1091 left: 0;
1092 margin: auto;
1093 right: 0;
1094 transform-origin: top center;
1095 transition: transform 250ms cubic-bezier(0.4, 0.0, 1, 1) .4s;
1096 z-index: 2;
1097}
1098</style>
1099 <script>// Copyright 2017 The Chromium Authors. All rights reserved.
1100// Use of this source code is governed by a BSD-style license that can be
1101// found in the LICENSE file.
1102
1103// This is the shared code for security interstitials. It is used for both SSL
1104// interstitials and Safe Browsing interstitials.
1105
1106// Should match security_interstitials::SecurityInterstitialCommand
1107/** @enum| {string} */
1108var SecurityInterstitialCommandId = {
1109 CMD_DONT_PROCEED: 0,
1110 CMD_PROCEED: 1,
1111 // Ways for user to get more information
1112 CMD_SHOW_MORE_SECTION: 2,
1113 CMD_OPEN_HELP_CENTER: 3,
1114 CMD_OPEN_DIAGNOSTIC: 4,
1115 // Primary button actions
1116 CMD_RELOAD: 5,
1117 CMD_OPEN_DATE_SETTINGS: 6,
1118 CMD_OPEN_LOGIN: 7,
1119 // Safe Browsing Extended Reporting
1120 CMD_DO_REPORT: 8,
1121 CMD_DONT_REPORT: 9,
1122 CMD_OPEN_REPORTING_PRIVACY: 10,
1123 CMD_OPEN_WHITEPAPER: 11,
1124 // Report a phishing error.
1125 CMD_REPORT_PHISHING_ERROR: 12
1126};
1127
1128var HIDDEN_CLASS = 'hidden';
1129
1130/**
1131 * A convenience method for sending commands to the parent page.
1132 * @param {string} cmd The command to send.
1133 */
1134function sendCommand(cmd) {
1135 if (window.certificateErrorPageController) {
1136 switch (cmd) {
1137 case SecurityInterstitialCommandId.CMD_DONT_PROCEED:
1138 certificateErrorPageController.dontProceed();
1139 break;
1140 case SecurityInterstitialCommandId.CMD_PROCEED:
1141 certificateErrorPageController.proceed();
1142 break;
1143 case SecurityInterstitialCommandId.CMD_SHOW_MORE_SECTION:
1144 certificateErrorPageController.showMoreSection();
1145 break;
1146 case SecurityInterstitialCommandId.CMD_OPEN_HELP_CENTER:
1147 certificateErrorPageController.openHelpCenter();
1148 break;
1149 case SecurityInterstitialCommandId.CMD_OPEN_DIAGNOSTIC:
1150 certificateErrorPageController.openDiagnostic();
1151 break;
1152 case SecurityInterstitialCommandId.CMD_RELOAD:
1153 certificateErrorPageController.reload();
1154 break;
1155 case SecurityInterstitialCommandId.CMD_OPEN_DATE_SETTINGS:
1156 certificateErrorPageController.openDateSettings();
1157 break;
1158 case SecurityInterstitialCommandId.CMD_OPEN_LOGIN:
1159 certificateErrorPageController.openLogin();
1160 break;
1161 case SecurityInterstitialCommandId.CMD_DO_REPORT:
1162 certificateErrorPageController.doReport();
1163 break;
1164 case SecurityInterstitialCommandId.CMD_DONT_REPORT:
1165 certificateErrorPageController.dontReport();
1166 break;
1167 case SecurityInterstitialCommandId.CMD_OPEN_REPORTING_PRIVACY:
1168 certificateErrorPageController.openReportingPrivacy();
1169 break;
1170 case SecurityInterstitialCommandId.CMD_OPEN_WHITEPAPER:
1171 certificateErrorPageController.openWhitepaper();
1172 break;
1173 case SecurityInterstitialCommandId.CMD_REPORT_PHISHING_ERROR:
1174 certificateErrorPageController.reportPhishingError();
1175 break;
1176 }
1177 return;
1178 }
1179//
1180 window.domAutomationController.send(cmd);
1181//
1182//
1183}
1184
1185/**
1186 * Call this to stop clicks on <a href="#"> links from scrolling to the top of
1187 * the page (and possibly showing a # in the link).
1188 */
1189function preventDefaultOnPoundLinkClicks() {
1190 document.addEventListener('click', function(e) {
1191 var anchor = findAncestor(/** @type {Node} */ (e.target), function(el) {
1192 return el.tagName == 'A';
1193 });
1194 // Use getAttribute() to prevent URL normalization.
1195 if (anchor && anchor.getAttribute('href') == '#')
1196 e.preventDefault();
1197 });
1198}
1199</script>
1200 <script>// Copyright 2015 The Chromium Authors. All rights reserved.
1201// Use of this source code is governed by a BSD-style license that can be
1202// found in the LICENSE file.
1203
1204var mobileNav = false;
1205
1206/**
1207 * For small screen mobile the navigation buttons are moved
1208 * below the advanced text.
1209 */
1210function onResize() {
1211 var helpOuterBox = document.querySelector('#details');
1212 var mainContent = document.querySelector('#main-content');
1213 var mediaQuery = '(min-width: 240px) and (max-width: 420px) and ' +
1214 '(min-height: 401px), ' +
1215 '(max-height: 560px) and (min-height: 240px) and ' +
1216 '(min-width: 421px)';
1217
1218 var detailsHidden = helpOuterBox.classList.contains(HIDDEN_CLASS);
1219 var runnerContainer = document.querySelector('.runner-container');
1220
1221 // Check for change in nav status.
1222 if (mobileNav != window.matchMedia(mediaQuery).matches) {
1223 mobileNav = !mobileNav;
1224
1225 // Handle showing the top content / details sections according to state.
1226 if (mobileNav) {
1227 mainContent.classList.toggle(HIDDEN_CLASS, !detailsHidden);
1228 helpOuterBox.classList.toggle(HIDDEN_CLASS, detailsHidden);
1229 if (runnerContainer) {
1230 runnerContainer.classList.toggle(HIDDEN_CLASS, !detailsHidden);
1231 }
1232 } else if (!detailsHidden) {
1233 // Non mobile nav with visible details.
1234 mainContent.classList.remove(HIDDEN_CLASS);
1235 helpOuterBox.classList.remove(HIDDEN_CLASS);
1236 if (runnerContainer) {
1237 runnerContainer.classList.remove(HIDDEN_CLASS);
1238 }
1239 }
1240 }
1241}
1242
1243function setupMobileNav() {
1244 window.addEventListener('resize', onResize);
1245 onResize();
1246}
1247
1248document.addEventListener('DOMContentLoaded', setupMobileNav);
1249</script>
1250 <script>// Copyright 2013 The Chromium Authors. All rights reserved.
1251// Use of this source code is governed by a BSD-style license that can be
1252// found in the LICENSE file.
1253
1254function toggleHelpBox() {
1255 var helpBoxOuter = document.getElementById('details');
1256 helpBoxOuter.classList.toggle(HIDDEN_CLASS);
1257 var detailsButton = document.getElementById('details-button');
1258 if (helpBoxOuter.classList.contains(HIDDEN_CLASS))
1259 detailsButton.innerText = detailsButton.detailsText;
1260 else
1261 detailsButton.innerText = detailsButton.hideDetailsText;
1262
1263 // Details appears over the main content on small screens.
1264 if (mobileNav) {
1265 document.getElementById('main-content').classList.toggle(HIDDEN_CLASS);
1266 var runnerContainer = document.querySelector('.runner-container');
1267 if (runnerContainer) {
1268 runnerContainer.classList.toggle(HIDDEN_CLASS);
1269 }
1270 }
1271}
1272
1273function diagnoseErrors() {
1274//
1275 if (window.errorPageController)
1276 errorPageController.diagnoseErrorsButtonClick();
1277//
1278//
1279}
1280
1281// Subframes use a different layout but the same html file. This is to make it
1282// easier to support platforms that load the error page via different
1283// mechanisms (Currently just iOS).
1284if (window.top.location != window.location)
1285 document.documentElement.setAttribute('subframe', '');
1286
1287// Re-renders the error page using |strings| as the dictionary of values.
1288// Used by NetErrorTabHelper to update DNS error pages with probe results.
1289function updateForDnsProbe(strings) {
1290 var context = new JsEvalContext(strings);
1291 jstProcess(context, document.getElementById('t'));
1292}
1293
1294// Given the classList property of an element, adds an icon class to the list
1295// and removes the previously-
1296function updateIconClass(classList, newClass) {
1297 var oldClass;
1298
1299 if (classList.hasOwnProperty('last_icon_class')) {
1300 oldClass = classList['last_icon_class'];
1301 if (oldClass == newClass)
1302 return;
1303 }
1304
1305 classList.add(newClass);
1306 if (oldClass !== undefined)
1307 classList.remove(oldClass);
1308
1309 classList['last_icon_class'] = newClass;
1310
1311 if (newClass == 'icon-offline') {
1312 document.body.classList.add('offline');
1313 new Runner('.interstitial-wrapper');
1314 } else {
1315 document.body.classList.add('neterror');
1316 }
1317}
1318
1319// Does a search using |baseSearchUrl| and the text in the search box.
1320function search(baseSearchUrl) {
1321 var searchTextNode = document.getElementById('search-box');
1322 document.location = baseSearchUrl + searchTextNode.value;
1323 return false;
1324}
1325
1326// Use to track clicks on elements generated by the navigation correction
1327// service. If |trackingId| is negative, the element does not come from the
1328// correction service.
1329function trackClick(trackingId) {
1330 // This can't be done with XHRs because XHRs are cancelled on navigation
1331 // start, and because these are cross-site requests.
1332 if (trackingId >= 0 && errorPageController)
1333 errorPageController.trackClick(trackingId);
1334}
1335
1336// Called when an <a> tag generated by the navigation correction service is
1337// clicked. Separate function from trackClick so the resources don't have to
1338// be updated if new data is added to jstdata.
1339function linkClicked(jstdata) {
1340 trackClick(jstdata.trackingId);
1341}
1342
1343// Implements button clicks. This function is needed during the transition
1344// between implementing these in trunk chromium and implementing them in
1345// iOS.
1346function reloadButtonClick(url) {
1347 if (window.errorPageController) {
1348 errorPageController.reloadButtonClick();
1349 } else {
1350 location = url;
1351 }
1352}
1353
1354function showSavedCopyButtonClick() {
1355 if (window.errorPageController) {
1356 errorPageController.showSavedCopyButtonClick();
1357 }
1358}
1359
1360function downloadButtonClick() {
1361 if (window.errorPageController) {
1362 errorPageController.downloadButtonClick();
1363 var downloadButton = document.getElementById('download-button');
1364 if (downloadButton.classList.contains('download-button-alternate')) {
1365 downloadButton.classList.add('download-button-alternate-disabled');
1366 }
1367 downloadButton.disabled = true;
1368 downloadButton.textContent = downloadButton.disabledText;
1369 }
1370}
1371
1372function detailsButtonClick() {
1373 if (window.errorPageController)
1374 errorPageController.detailsButtonClick();
1375}
1376
1377/**
1378 * Replace the reload button with the Google cached copy suggestion.
1379 */
1380function setUpCachedButton(buttonStrings) {
1381 var reloadButton = document.getElementById('reload-button');
1382
1383 reloadButton.textContent = buttonStrings.msg;
1384 var url = buttonStrings.cacheUrl;
1385 var trackingId = buttonStrings.trackingId;
1386 reloadButton.onclick = function(e) {
1387 e.preventDefault();
1388 trackClick(trackingId);
1389 if (window.errorPageController) {
1390 errorPageController.trackCachedCopyButtonClick();
1391 }
1392 location = url;
1393 };
1394 reloadButton.style.display = '';
1395 document.getElementById('control-buttons').hidden = false;
1396}
1397
1398var primaryControlOnLeft = true;
1399//
1400
1401function onDocumentLoad() {
1402 var controlButtonDiv = document.getElementById('control-buttons');
1403 var reloadButton = document.getElementById('reload-button');
1404 var detailsButton = document.getElementById('details-button');
1405 var showSavedCopyButton = document.getElementById('show-saved-copy-button');
1406 var downloadButton = document.getElementById('download-button');
1407
1408 var reloadButtonVisible = loadTimeData.valueExists('reloadButton') &&
1409 loadTimeData.getValue('reloadButton').msg;
1410 var showSavedCopyButtonVisible =
1411 loadTimeData.valueExists('showSavedCopyButton') &&
1412 loadTimeData.getValue('showSavedCopyButton').msg;
1413 var downloadButtonVisible =
1414 loadTimeData.valueExists('downloadButton') &&
1415 loadTimeData.getValue('downloadButton').msg;
1416
1417 var primaryButton, secondaryButton;
1418 if (showSavedCopyButton.primary) {
1419 primaryButton = showSavedCopyButton;
1420 secondaryButton = reloadButton;
1421 } else {
1422 primaryButton = reloadButton;
1423 secondaryButton = showSavedCopyButton;
1424 }
1425
1426 // Sets up the proper button layout for the current platform.
1427 if (primaryControlOnLeft) {
1428 buttons.classList.add('suggested-left');
1429 controlButtonDiv.insertBefore(secondaryButton, primaryButton);
1430 } else {
1431 buttons.classList.add('suggested-right');
1432 controlButtonDiv.insertBefore(primaryButton, secondaryButton);
1433 }
1434
1435 // Check for Google cached copy suggestion.
1436 if (loadTimeData.valueExists('cacheButton')) {
1437 setUpCachedButton(loadTimeData.getValue('cacheButton'));
1438 }
1439
1440 if (reloadButton.style.display == 'none' &&
1441 showSavedCopyButton.style.display == 'none' &&
1442 downloadButton.style.display == 'none') {
1443 detailsButton.classList.add('singular');
1444 }
1445
1446 // Check for customization of downloadButton style if it's visible.
1447 if (downloadButtonVisible &&
1448 loadTimeData.valueExists('alternateDownloadButtonStyle')) {
1449 downloadButton.classList.add('download-button-alternate');
1450 reloadButton.disabled = true;
1451 reloadButton.classList.add('reload-button-alternate');
1452 } else if (downloadButtonVisible) {
1453 downloadButton.classList.add('download-button');
1454 }
1455
1456 // Show control buttons.
1457 if (reloadButtonVisible || showSavedCopyButtonVisible ||
1458 downloadButtonVisible) {
1459 controlButtonDiv.hidden = false;
1460
1461 // Set the secondary button state in the cases of two call to actions.
1462 if ((reloadButtonVisible || downloadButtonVisible) &&
1463 showSavedCopyButtonVisible) {
1464 secondaryButton.classList.add('secondary-button');
1465 }
1466 }
1467}
1468
1469document.addEventListener('DOMContentLoaded', onDocumentLoad);
1470</script>
1471 <script>// Copyright (c) 2014 The Chromium Authors. All rights reserved.
1472// Use of this source code is governed by a BSD-style license that can be
1473// found in the LICENSE file.
1474(function() {
1475'use strict';
1476/**
1477 * T-Rex runner.
1478 * @param {string} outerContainerId Outer containing element id.
1479 * @param {Object} opt_config
1480 * @constructor
1481 * @export
1482 */
1483function Runner(outerContainerId, opt_config) {
1484 // Singleton
1485 if (Runner.instance_) {
1486 return Runner.instance_;
1487 }
1488 Runner.instance_ = this;
1489
1490 this.outerContainerEl = document.querySelector(outerContainerId);
1491 this.containerEl = null;
1492 this.snackbarEl = null;
1493
1494 this.config = opt_config || Runner.config;
1495 // Logical dimensions of the container.
1496 this.dimensions = Runner.defaultDimensions;
1497
1498 this.canvas = null;
1499 this.canvasCtx = null;
1500
1501 this.tRex = null;
1502
1503 this.distanceMeter = null;
1504 this.distanceRan = 0;
1505
1506 this.highestScore = 0;
1507
1508 this.time = 0;
1509 this.runningTime = 0;
1510 this.msPerFrame = 1000 / FPS;
1511 this.currentSpeed = this.config.SPEED;
1512
1513 this.obstacles = [];
1514
1515 this.activated = false; // Whether the easter egg has been activated.
1516 this.playing = false; // Whether the game is currently in play state.
1517 this.crashed = false;
1518 this.paused = false;
1519 this.inverted = false;
1520 this.invertTimer = 0;
1521 this.resizeTimerId_ = null;
1522
1523 this.playCount = 0;
1524
1525 // Sound FX.
1526 this.audioBuffer = null;
1527 this.soundFx = {};
1528
1529 // Global web audio context for playing sounds.
1530 this.audioContext = null;
1531
1532 // Images.
1533 this.images = {};
1534 this.imagesLoaded = 0;
1535
1536 if (this.isDisabled()) {
1537 this.setupDisabledRunner();
1538 } else {
1539 this.loadImages();
1540 }
1541}
1542window['Runner'] = Runner;
1543
1544
1545/**
1546 * Default game width.
1547 * @const
1548 */
1549var DEFAULT_WIDTH = 600;
1550
1551/**
1552 * Frames per second.
1553 * @const
1554 */
1555var FPS = 60;
1556
1557/** @const */
1558var IS_HIDPI = window.devicePixelRatio > 1;
1559
1560/** @const */
1561var IS_IOS = /iPad|iPhone|iPod/.test(window.navigator.platform);
1562
1563/** @const */
1564var IS_MOBILE = /Android/.test(window.navigator.userAgent) || IS_IOS;
1565
1566/** @const */
1567var ARCADE_MODE_URL = 'chrome://dino/';
1568
1569/**
1570 * Default game configuration.
1571 * @enum {number}
1572 */
1573Runner.config = {
1574 ACCELERATION: 0.001,
1575 BG_CLOUD_SPEED: 0.2,
1576 BOTTOM_PAD: 10,
1577 CLEAR_TIME: 3000,
1578 CLOUD_FREQUENCY: 0.5,
1579 GAMEOVER_CLEAR_TIME: 750,
1580 GAP_COEFFICIENT: 0.6,
1581 GRAVITY: 0.6,
1582 INITIAL_JUMP_VELOCITY: 12,
1583 INVERT_FADE_DURATION: 12000,
1584 INVERT_DISTANCE: 700,
1585 MAX_BLINK_COUNT: 3,
1586 MAX_CLOUDS: 6,
1587 MAX_OBSTACLE_LENGTH: 3,
1588 MAX_OBSTACLE_DUPLICATION: 2,
1589 MAX_SPEED: 13,
1590 MIN_JUMP_HEIGHT: 35,
1591 MOBILE_SPEED_COEFFICIENT: 1.2,
1592 RESOURCE_TEMPLATE_ID: 'audio-resources',
1593 SPEED: 6,
1594 SPEED_DROP_COEFFICIENT: 3,
1595 ARCADE_MODE_INITIAL_TOP_POSITION: 35,
1596 ARCADE_MODE_TOP_POSITION_PERCENT: 0.1
1597};
1598
1599
1600/**
1601 * Default dimensions.
1602 * @enum {string}
1603 */
1604Runner.defaultDimensions = {
1605 WIDTH: DEFAULT_WIDTH,
1606 HEIGHT: 150
1607};
1608
1609
1610/**
1611 * CSS class names.
1612 * @enum {string}
1613 */
1614Runner.classes = {
1615 ARCADE_MODE: 'arcade-mode',
1616 CANVAS: 'runner-canvas',
1617 CONTAINER: 'runner-container',
1618 CRASHED: 'crashed',
1619 ICON: 'icon-offline',
1620 INVERTED: 'inverted',
1621 SNACKBAR: 'snackbar',
1622 SNACKBAR_SHOW: 'snackbar-show',
1623 TOUCH_CONTROLLER: 'controller'
1624};
1625
1626
1627/**
1628 * Sprite definition layout of the spritesheet.
1629 * @enum {Object}
1630 */
1631Runner.spriteDefinition = {
1632 LDPI: {
1633 CACTUS_LARGE: {x: 332, y: 2},
1634 CACTUS_SMALL: {x: 228, y: 2},
1635 CLOUD: {x: 86, y: 2},
1636 HORIZON: {x: 2, y: 54},
1637 MOON: {x: 484, y: 2},
1638 PTERODACTYL: {x: 134, y: 2},
1639 RESTART: {x: 2, y: 2},
1640 TEXT_SPRITE: {x: 655, y: 2},
1641 TREX: {x: 848, y: 2},
1642 STAR: {x: 645, y: 2}
1643 },
1644 HDPI: {
1645 CACTUS_LARGE: {x: 652, y: 2},
1646 CACTUS_SMALL: {x: 446, y: 2},
1647 CLOUD: {x: 166, y: 2},
1648 HORIZON: {x: 2, y: 104},
1649 MOON: {x: 954, y: 2},
1650 PTERODACTYL: {x: 260, y: 2},
1651 RESTART: {x: 2, y: 2},
1652 TEXT_SPRITE: {x: 1294, y: 2},
1653 TREX: {x: 1678, y: 2},
1654 STAR: {x: 1276, y: 2}
1655 }
1656};
1657
1658
1659/**
1660 * Sound FX. Reference to the ID of the audio tag on interstitial page.
1661 * @enum {string}
1662 */
1663Runner.sounds = {
1664 BUTTON_PRESS: 'offline-sound-press',
1665 HIT: 'offline-sound-hit',
1666 SCORE: 'offline-sound-reached'
1667};
1668
1669
1670/**
1671 * Key code mapping.
1672 * @enum {Object}
1673 */
1674Runner.keycodes = {
1675 JUMP: {'38': 1, '32': 1}, // Up, spacebar
1676 DUCK: {'40': 1}, // Down
1677 RESTART: {'13': 1} // Enter
1678};
1679
1680
1681/**
1682 * Runner event names.
1683 * @enum {string}
1684 */
1685Runner.events = {
1686 ANIM_END: 'webkitAnimationEnd',
1687 CLICK: 'click',
1688 KEYDOWN: 'keydown',
1689 KEYUP: 'keyup',
1690 POINTERDOWN: 'pointerdown',
1691 POINTERUP: 'pointerup',
1692 RESIZE: 'resize',
1693 TOUCHEND: 'touchend',
1694 TOUCHSTART: 'touchstart',
1695 VISIBILITY: 'visibilitychange',
1696 BLUR: 'blur',
1697 FOCUS: 'focus',
1698 LOAD: 'load'
1699};
1700
1701Runner.prototype = {
1702 /**
1703 * Whether the easter egg has been disabled. CrOS enterprise enrolled devices.
1704 * @return {boolean}
1705 */
1706 isDisabled: function() {
1707 return loadTimeData && loadTimeData.valueExists('disabledEasterEgg');
1708 },
1709
1710 /**
1711 * For disabled instances, set up a snackbar with the disabled message.
1712 */
1713 setupDisabledRunner: function() {
1714 this.containerEl = document.createElement('div');
1715 this.containerEl.className = Runner.classes.SNACKBAR;
1716 this.containerEl.textContent = loadTimeData.getValue('disabledEasterEgg');
1717 this.outerContainerEl.appendChild(this.containerEl);
1718
1719 // Show notification when the activation key is pressed.
1720 document.addEventListener(Runner.events.KEYDOWN, function(e) {
1721 if (Runner.keycodes.JUMP[e.keyCode]) {
1722 this.containerEl.classList.add(Runner.classes.SNACKBAR_SHOW);
1723 document.querySelector('.icon').classList.add('icon-disabled');
1724 }
1725 }.bind(this));
1726 },
1727
1728 /**
1729 * Setting individual settings for debugging.
1730 * @param {string} setting
1731 * @param {*} value
1732 */
1733 updateConfigSetting: function(setting, value) {
1734 if (setting in this.config && value != undefined) {
1735 this.config[setting] = value;
1736
1737 switch (setting) {
1738 case 'GRAVITY':
1739 case 'MIN_JUMP_HEIGHT':
1740 case 'SPEED_DROP_COEFFICIENT':
1741 this.tRex.config[setting] = value;
1742 break;
1743 case 'INITIAL_JUMP_VELOCITY':
1744 this.tRex.setJumpVelocity(value);
1745 break;
1746 case 'SPEED':
1747 this.setSpeed(value);
1748 break;
1749 }
1750 }
1751 },
1752
1753 /**
1754 * Cache the appropriate image sprite from the page and get the sprite sheet
1755 * definition.
1756 */
1757 loadImages: function() {
1758 if (IS_HIDPI) {
1759 Runner.imageSprite = document.getElementById('offline-resources-2x');
1760 this.spriteDef = Runner.spriteDefinition.HDPI;
1761 } else {
1762 Runner.imageSprite = document.getElementById('offline-resources-1x');
1763 this.spriteDef = Runner.spriteDefinition.LDPI;
1764 }
1765
1766 if (Runner.imageSprite.complete) {
1767 this.init();
1768 } else {
1769 // If the images are not yet loaded, add a listener.
1770 Runner.imageSprite.addEventListener(Runner.events.LOAD,
1771 this.init.bind(this));
1772 }
1773 },
1774
1775 /**
1776 * Load and decode base 64 encoded sounds.
1777 */
1778 loadSounds: function() {
1779 if (!IS_IOS) {
1780 this.audioContext = new AudioContext();
1781
1782 var resourceTemplate =
1783 document.getElementById(this.config.RESOURCE_TEMPLATE_ID).content;
1784
1785 for (var sound in Runner.sounds) {
1786 var soundSrc =
1787 resourceTemplate.getElementById(Runner.sounds[sound]).src;
1788 soundSrc = soundSrc.substr(soundSrc.indexOf(',') + 1);
1789 var buffer = decodeBase64ToArrayBuffer(soundSrc);
1790
1791 // Async, so no guarantee of order in array.
1792 this.audioContext.decodeAudioData(buffer, function(index, audioData) {
1793 this.soundFx[index] = audioData;
1794 }.bind(this, sound));
1795 }
1796 }
1797 },
1798
1799 /**
1800 * Sets the game speed. Adjust the speed accordingly if on a smaller screen.
1801 * @param {number} opt_speed
1802 */
1803 setSpeed: function(opt_speed) {
1804 var speed = opt_speed || this.currentSpeed;
1805
1806 // Reduce the speed on smaller mobile screens.
1807 if (this.dimensions.WIDTH < DEFAULT_WIDTH) {
1808 var mobileSpeed = speed * this.dimensions.WIDTH / DEFAULT_WIDTH *
1809 this.config.MOBILE_SPEED_COEFFICIENT;
1810 this.currentSpeed = mobileSpeed > speed ? speed : mobileSpeed;
1811 } else if (opt_speed) {
1812 this.currentSpeed = opt_speed;
1813 }
1814 },
1815
1816 /**
1817 * Game initialiser.
1818 */
1819 init: function() {
1820 // Hide the static icon.
1821 document.querySelector('.' + Runner.classes.ICON).style.visibility =
1822 'hidden';
1823
1824 this.adjustDimensions();
1825 this.setSpeed();
1826
1827 this.containerEl = document.createElement('div');
1828 this.containerEl.className = Runner.classes.CONTAINER;
1829
1830 // Player canvas container.
1831 this.canvas = createCanvas(this.containerEl, this.dimensions.WIDTH,
1832 this.dimensions.HEIGHT, Runner.classes.PLAYER);
1833
1834 this.canvasCtx = this.canvas.getContext('2d');
1835 this.canvasCtx.fillStyle = '#f7f7f7';
1836 this.canvasCtx.fill();
1837 Runner.updateCanvasScaling(this.canvas);
1838
1839 // Horizon contains clouds, obstacles and the ground.
1840 this.horizon = new Horizon(this.canvas, this.spriteDef, this.dimensions,
1841 this.config.GAP_COEFFICIENT);
1842
1843 // Distance meter
1844 this.distanceMeter = new DistanceMeter(this.canvas,
1845 this.spriteDef.TEXT_SPRITE, this.dimensions.WIDTH);
1846
1847 // Draw t-rex
1848 this.tRex = new Trex(this.canvas, this.spriteDef.TREX);
1849
1850 this.outerContainerEl.appendChild(this.containerEl);
1851
1852 this.startListening();
1853 this.update();
1854
1855 window.addEventListener(Runner.events.RESIZE,
1856 this.debounceResize.bind(this));
1857 },
1858
1859 /**
1860 * Create the touch controller. A div that covers whole screen.
1861 */
1862 createTouchController: function() {
1863 this.touchController = document.createElement('div');
1864 this.touchController.className = Runner.classes.TOUCH_CONTROLLER;
1865 this.touchController.addEventListener(Runner.events.TOUCHSTART, this);
1866 this.touchController.addEventListener(Runner.events.TOUCHEND, this);
1867 },
1868
1869 /**
1870 * Debounce the resize event.
1871 */
1872 debounceResize: function() {
1873 if (!this.resizeTimerId_) {
1874 this.resizeTimerId_ =
1875 setInterval(this.adjustDimensions.bind(this), 250);
1876 }
1877 },
1878
1879 /**
1880 * Adjust game space dimensions on resize.
1881 */
1882 adjustDimensions: function() {
1883 clearInterval(this.resizeTimerId_);
1884 this.resizeTimerId_ = null;
1885
1886 var boxStyles = window.getComputedStyle(this.outerContainerEl);
1887 var padding = Number(boxStyles.paddingLeft.substr(0,
1888 boxStyles.paddingLeft.length - 2));
1889
1890 this.dimensions.WIDTH = this.outerContainerEl.offsetWidth - padding * 2;
1891 if (this.isArcadeMode()) {
1892 this.dimensions.WIDTH = Math.min(DEFAULT_WIDTH, this.dimensions.WIDTH);
1893 if (this.activated) {
1894 this.setArcadeModeContainerScale();
1895 }
1896 }
1897
1898 // Redraw the elements back onto the canvas.
1899 if (this.canvas) {
1900 this.canvas.width = this.dimensions.WIDTH;
1901 this.canvas.height = this.dimensions.HEIGHT;
1902
1903 Runner.updateCanvasScaling(this.canvas);
1904
1905 this.distanceMeter.calcXPos(this.dimensions.WIDTH);
1906 this.clearCanvas();
1907 this.horizon.update(0, 0, true);
1908 this.tRex.update(0);
1909
1910 // Outer container and distance meter.
1911 if (this.playing || this.crashed || this.paused) {
1912 this.containerEl.style.width = this.dimensions.WIDTH + 'px';
1913 this.containerEl.style.height = this.dimensions.HEIGHT + 'px';
1914 this.distanceMeter.update(0, Math.ceil(this.distanceRan));
1915 this.stop();
1916 } else {
1917 this.tRex.draw(0, 0);
1918 }
1919
1920 // Game over panel.
1921 if (this.crashed && this.gameOverPanel) {
1922 this.gameOverPanel.updateDimensions(this.dimensions.WIDTH);
1923 this.gameOverPanel.draw();
1924 }
1925 }
1926 },
1927
1928 /**
1929 * Play the game intro.
1930 * Canvas container width expands out to the full width.
1931 */
1932 playIntro: function() {
1933 if (!this.activated && !this.crashed) {
1934 this.playingIntro = true;
1935 this.tRex.playingIntro = true;
1936
1937 // CSS animation definition.
1938 var keyframes = '@-webkit-keyframes intro { ' +
1939 'from { width:' + Trex.config.WIDTH + 'px }' +
1940 'to { width: ' + this.dimensions.WIDTH + 'px }' +
1941 '}';
1942 document.styleSheets[0].insertRule(keyframes, 0);
1943
1944 this.containerEl.addEventListener(Runner.events.ANIM_END,
1945 this.startGame.bind(this));
1946
1947 this.containerEl.style.webkitAnimation = 'intro .4s ease-out 1 both';
1948 this.containerEl.style.width = this.dimensions.WIDTH + 'px';
1949
1950 if (this.touchController) {
1951 this.outerContainerEl.appendChild(this.touchController);
1952 }
1953 this.playing = true;
1954 this.activated = true;
1955 } else if (this.crashed) {
1956 this.restart();
1957 }
1958 },
1959
1960
1961 /**
1962 * Update the game status to started.
1963 */
1964 startGame: function() {
1965 if (this.isArcadeMode()) {
1966 this.setArcadeMode();
1967 }
1968 this.runningTime = 0;
1969 this.playingIntro = false;
1970 this.tRex.playingIntro = false;
1971 this.containerEl.style.webkitAnimation = '';
1972 this.playCount++;
1973
1974 // Handle tabbing off the page. Pause the current game.
1975 document.addEventListener(Runner.events.VISIBILITY,
1976 this.onVisibilityChange.bind(this));
1977
1978 window.addEventListener(Runner.events.BLUR,
1979 this.onVisibilityChange.bind(this));
1980
1981 window.addEventListener(Runner.events.FOCUS,
1982 this.onVisibilityChange.bind(this));
1983 },
1984
1985 clearCanvas: function() {
1986 this.canvasCtx.clearRect(0, 0, this.dimensions.WIDTH,
1987 this.dimensions.HEIGHT);
1988 },
1989
1990 /**
1991 * Update the game frame and schedules the next one.
1992 */
1993 update: function() {
1994 this.updatePending = false;
1995
1996 var now = getTimeStamp();
1997 var deltaTime = now - (this.time || now);
1998 this.time = now;
1999
2000 if (this.playing) {
2001 this.clearCanvas();
2002
2003 if (this.tRex.jumping) {
2004 this.tRex.updateJump(deltaTime);
2005 }
2006
2007 this.runningTime += deltaTime;
2008 var hasObstacles = this.runningTime > this.config.CLEAR_TIME;
2009
2010 // First jump triggers the intro.
2011 if (this.tRex.jumpCount == 1 && !this.playingIntro) {
2012 this.playIntro();
2013 }
2014
2015 // The horizon doesn't move until the intro is over.
2016 if (this.playingIntro) {
2017 this.horizon.update(0, this.currentSpeed, hasObstacles);
2018 } else {
2019 deltaTime = !this.activated ? 0 : deltaTime;
2020 this.horizon.update(deltaTime, this.currentSpeed, hasObstacles,
2021 this.inverted);
2022 }
2023
2024 // Check for collisions.
2025 var collision = hasObstacles &&
2026 checkForCollision(this.horizon.obstacles[0], this.tRex);
2027
2028 if (!collision) {
2029 this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame;
2030
2031 if (this.currentSpeed < this.config.MAX_SPEED) {
2032 this.currentSpeed += this.config.ACCELERATION;
2033 }
2034 } else {
2035 this.gameOver();
2036 }
2037
2038 var playAchievementSound = this.distanceMeter.update(deltaTime,
2039 Math.ceil(this.distanceRan));
2040
2041 if (playAchievementSound) {
2042 this.playSound(this.soundFx.SCORE);
2043 }
2044
2045 // Night mode.
2046 if (this.invertTimer > this.config.INVERT_FADE_DURATION) {
2047 this.invertTimer = 0;
2048 this.invertTrigger = false;
2049 this.invert();
2050 } else if (this.invertTimer) {
2051 this.invertTimer += deltaTime;
2052 } else {
2053 var actualDistance =
2054 this.distanceMeter.getActualDistance(Math.ceil(this.distanceRan));
2055
2056 if (actualDistance > 0) {
2057 this.invertTrigger = !(actualDistance %
2058 this.config.INVERT_DISTANCE);
2059
2060 if (this.invertTrigger && this.invertTimer === 0) {
2061 this.invertTimer += deltaTime;
2062 this.invert();
2063 }
2064 }
2065 }
2066 }
2067
2068 if (this.playing || (!this.activated &&
2069 this.tRex.blinkCount < Runner.config.MAX_BLINK_COUNT)) {
2070 this.tRex.update(deltaTime);
2071 this.scheduleNextUpdate();
2072 }
2073 },
2074
2075 /**
2076 * Event handler.
2077 */
2078 handleEvent: function(e) {
2079 return (function(evtType, events) {
2080 switch (evtType) {
2081 case events.KEYDOWN:
2082 case events.TOUCHSTART:
2083 case events.POINTERDOWN:
2084 this.onKeyDown(e);
2085 break;
2086 case events.KEYUP:
2087 case events.TOUCHEND:
2088 case events.POINTERUP:
2089 this.onKeyUp(e);
2090 break;
2091 }
2092 }.bind(this))(e.type, Runner.events);
2093 },
2094
2095 /**
2096 * Bind relevant key / mouse / touch listeners.
2097 */
2098 startListening: function() {
2099 // Keys.
2100 document.addEventListener(Runner.events.KEYDOWN, this);
2101 document.addEventListener(Runner.events.KEYUP, this);
2102
2103 // Touch / pointer.
2104 this.containerEl.addEventListener(Runner.events.TOUCHSTART, this);
2105 document.addEventListener(Runner.events.POINTERDOWN, this);
2106 document.addEventListener(Runner.events.POINTERUP, this);
2107 },
2108
2109 /**
2110 * Remove all listeners.
2111 */
2112 stopListening: function() {
2113 document.removeEventListener(Runner.events.KEYDOWN, this);
2114 document.removeEventListener(Runner.events.KEYUP, this);
2115
2116 if (this.touchController) {
2117 this.touchController.removeEventListener(Runner.events.TOUCHSTART, this);
2118 this.touchController.removeEventListener(Runner.events.TOUCHEND, this);
2119 }
2120
2121 this.containerEl.removeEventListener(Runner.events.TOUCHSTART, this);
2122 document.removeEventListener(Runner.events.POINTERDOWN, this);
2123 document.removeEventListener(Runner.events.POINTERUP, this);
2124 },
2125
2126 /**
2127 * Process keydown.
2128 * @param {Event} e
2129 */
2130 onKeyDown: function(e) {
2131 // Prevent native page scrolling whilst tapping on mobile.
2132 if (IS_MOBILE && this.playing) {
2133 e.preventDefault();
2134 }
2135
2136 if (!this.crashed && !this.paused) {
2137 if (Runner.keycodes.JUMP[e.keyCode] ||
2138 e.type == Runner.events.TOUCHSTART) {
2139 e.preventDefault();
2140 // Starting the game for the first time.
2141 if (!this.playing) {
2142 // Started by touch so create a touch controller.
2143 if (!this.touchController && e.type == Runner.events.TOUCHSTART) {
2144 this.createTouchController();
2145 }
2146 this.loadSounds();
2147 this.playing = true;
2148 this.update();
2149 if (window.errorPageController) {
2150 errorPageController.trackEasterEgg();
2151 }
2152 }
2153 // Start jump.
2154 if (!this.tRex.jumping && !this.tRex.ducking) {
2155 this.playSound(this.soundFx.BUTTON_PRESS);
2156 this.tRex.startJump(this.currentSpeed);
2157 }
2158 } else if (this.playing && Runner.keycodes.DUCK[e.keyCode]) {
2159 e.preventDefault();
2160 if (this.tRex.jumping) {
2161 // Speed drop, activated only when jump key is not pressed.
2162 this.tRex.setSpeedDrop();
2163 } else if (!this.tRex.jumping && !this.tRex.ducking) {
2164 // Duck.
2165 this.tRex.setDuck(true);
2166 }
2167 }
2168 } else if (this.crashed && e.type == Runner.events.TOUCHSTART &&
2169 e.currentTarget == this.containerEl) {
2170 this.restart();
2171 }
2172 },
2173
2174
2175 /**
2176 * Process key up.
2177 * @param {Event} e
2178 */
2179 onKeyUp: function(e) {
2180 var keyCode = String(e.keyCode);
2181 var isjumpKey = Runner.keycodes.JUMP[keyCode] ||
2182 e.type == Runner.events.TOUCHEND ||
2183 e.type == Runner.events.POINTERUP;
2184
2185 if (this.isRunning() && isjumpKey) {
2186 this.tRex.endJump();
2187 } else if (Runner.keycodes.DUCK[keyCode]) {
2188 this.tRex.speedDrop = false;
2189 this.tRex.setDuck(false);
2190 } else if (this.crashed) {
2191 // Check that enough time has elapsed before allowing jump key to restart.
2192 var deltaTime = getTimeStamp() - this.time;
2193
2194 if (Runner.keycodes.RESTART[keyCode] || this.isLeftClickOnCanvas(e) ||
2195 (deltaTime >= this.config.GAMEOVER_CLEAR_TIME &&
2196 Runner.keycodes.JUMP[keyCode])) {
2197 this.restart();
2198 }
2199 } else if (this.paused && isjumpKey) {
2200 // Reset the jump state
2201 this.tRex.reset();
2202 this.play();
2203 }
2204 },
2205
2206 /**
2207 * Returns whether the event was a left click on canvas.
2208 * On Windows right click is registered as a click.
2209 * @param {Event} e
2210 * @return {boolean}
2211 */
2212 isLeftClickOnCanvas: function(e) {
2213 return e.button != null && e.button < 2 &&
2214 e.type == Runner.events.POINTERUP && e.target == this.canvas;
2215 },
2216
2217 /**
2218 * RequestAnimationFrame wrapper.
2219 */
2220 scheduleNextUpdate: function() {
2221 if (!this.updatePending) {
2222 this.updatePending = true;
2223 this.raqId = requestAnimationFrame(this.update.bind(this));
2224 }
2225 },
2226
2227 /**
2228 * Whether the game is running.
2229 * @return {boolean}
2230 */
2231 isRunning: function() {
2232 return !!this.raqId;
2233 },
2234
2235 /**
2236 * Game over state.
2237 */
2238 gameOver: function() {
2239 this.playSound(this.soundFx.HIT);
2240 vibrate(200);
2241
2242 this.stop();
2243 this.crashed = true;
2244 this.distanceMeter.achievement = false;
2245
2246 this.tRex.update(100, Trex.status.CRASHED);
2247
2248 // Game over panel.
2249 if (!this.gameOverPanel) {
2250 this.gameOverPanel = new GameOverPanel(this.canvas,
2251 this.spriteDef.TEXT_SPRITE, this.spriteDef.RESTART,
2252 this.dimensions);
2253 } else {
2254 this.gameOverPanel.draw();
2255 }
2256
2257 // Update the high score.
2258 if (this.distanceRan > this.highestScore) {
2259 this.highestScore = Math.ceil(this.distanceRan);
2260 this.distanceMeter.setHighScore(this.highestScore);
2261 }
2262
2263 // Reset the time clock.
2264 this.time = getTimeStamp();
2265 },
2266
2267 stop: function() {
2268 this.playing = false;
2269 this.paused = true;
2270 cancelAnimationFrame(this.raqId);
2271 this.raqId = 0;
2272 },
2273
2274 play: function() {
2275 if (!this.crashed) {
2276 this.playing = true;
2277 this.paused = false;
2278 this.tRex.update(0, Trex.status.RUNNING);
2279 this.time = getTimeStamp();
2280 this.update();
2281 }
2282 },
2283
2284 restart: function() {
2285 if (!this.raqId) {
2286 this.playCount++;
2287 this.runningTime = 0;
2288 this.playing = true;
2289 this.paused = false;
2290 this.crashed = false;
2291 this.distanceRan = 0;
2292 this.setSpeed(this.config.SPEED);
2293 this.time = getTimeStamp();
2294 this.containerEl.classList.remove(Runner.classes.CRASHED);
2295 this.clearCanvas();
2296 this.distanceMeter.reset(this.highestScore);
2297 this.horizon.reset();
2298 this.tRex.reset();
2299 this.playSound(this.soundFx.BUTTON_PRESS);
2300 this.invert(true);
2301 this.update();
2302 }
2303 },
2304
2305 /**
2306 * Whether the game should go into arcade mode.
2307 * @return {boolean}
2308 */
2309 isArcadeMode: function() {
2310 return document.title == ARCADE_MODE_URL;
2311 },
2312
2313 /**
2314 * Hides offline messaging for a fullscreen game only experience.
2315 */
2316 setArcadeMode: function() {
2317 document.body.classList.add(Runner.classes.ARCADE_MODE);
2318 this.setArcadeModeContainerScale();
2319 },
2320
2321 /**
2322 * Sets the scaling for arcade mode.
2323 */
2324 setArcadeModeContainerScale: function() {
2325 var windowHeight = window.innerHeight;
2326 var scaleHeight = windowHeight / this.dimensions.HEIGHT;
2327 var scaleWidth = window.innerWidth / this.dimensions.WIDTH;
2328 var scale = Math.max(1, Math.min(scaleHeight, scaleWidth));
2329 var scaledCanvasHeight = this.dimensions.HEIGHT * scale;
2330 // Positions the game container at 10% of the available vertical window
2331 // height minus the game container height.
2332 var translateY = Math.ceil(Math.max(0, (windowHeight - scaledCanvasHeight -
2333 Runner.config.ARCADE_MODE_INITIAL_TOP_POSITION) *
2334 Runner.config.ARCADE_MODE_TOP_POSITION_PERCENT)) *
2335 window.devicePixelRatio;
2336 this.containerEl.style.transform = 'scale(' + scale + ') translateY(' +
2337 translateY + 'px)';
2338 },
2339
2340 /**
2341 * Pause the game if the tab is not in focus.
2342 */
2343 onVisibilityChange: function(e) {
2344 if (document.hidden || document.webkitHidden || e.type == 'blur' ||
2345 document.visibilityState != 'visible') {
2346 this.stop();
2347 } else if (!this.crashed) {
2348 this.tRex.reset();
2349 this.play();
2350 }
2351 },
2352
2353 /**
2354 * Play a sound.
2355 * @param {SoundBuffer} soundBuffer
2356 */
2357 playSound: function(soundBuffer) {
2358 if (soundBuffer) {
2359 var sourceNode = this.audioContext.createBufferSource();
2360 sourceNode.buffer = soundBuffer;
2361 sourceNode.connect(this.audioContext.destination);
2362 sourceNode.start(0);
2363 }
2364 },
2365
2366 /**
2367 * Inverts the current page / canvas colors.
2368 * @param {boolean} Whether to reset colors.
2369 */
2370 invert: function(reset) {
2371 if (reset) {
2372 document.body.classList.toggle(Runner.classes.INVERTED, false);
2373 this.invertTimer = 0;
2374 this.inverted = false;
2375 } else {
2376 this.inverted = document.body.classList.toggle(Runner.classes.INVERTED,
2377 this.invertTrigger);
2378 }
2379 }
2380};
2381
2382
2383/**
2384 * Updates the canvas size taking into
2385 * account the backing store pixel ratio and
2386 * the device pixel ratio.
2387 *
2388 * See article by Paul Lewis:
2389 * http://www.html5rocks.com/en/tutorials/canvas/hidpi/
2390 *
2391 * @param {HTMLCanvasElement} canvas
2392 * @param {number} opt_width
2393 * @param {number} opt_height
2394 * @return {boolean} Whether the canvas was scaled.
2395 */
2396Runner.updateCanvasScaling = function(canvas, opt_width, opt_height) {
2397 var context = canvas.getContext('2d');
2398
2399 // Query the various pixel ratios
2400 var devicePixelRatio = Math.floor(window.devicePixelRatio) || 1;
2401 var backingStoreRatio = Math.floor(context.webkitBackingStorePixelRatio) || 1;
2402 var ratio = devicePixelRatio / backingStoreRatio;
2403
2404 // Upscale the canvas if the two ratios don't match
2405 if (devicePixelRatio !== backingStoreRatio) {
2406 var oldWidth = opt_width || canvas.width;
2407 var oldHeight = opt_height || canvas.height;
2408
2409 canvas.width = oldWidth * ratio;
2410 canvas.height = oldHeight * ratio;
2411
2412 canvas.style.width = oldWidth + 'px';
2413 canvas.style.height = oldHeight + 'px';
2414
2415 // Scale the context to counter the fact that we've manually scaled
2416 // our canvas element.
2417 context.scale(ratio, ratio);
2418 return true;
2419 } else if (devicePixelRatio == 1) {
2420 // Reset the canvas width / height. Fixes scaling bug when the page is
2421 // zoomed and the devicePixelRatio changes accordingly.
2422 canvas.style.width = canvas.width + 'px';
2423 canvas.style.height = canvas.height + 'px';
2424 }
2425 return false;
2426};
2427
2428
2429/**
2430 * Get random number.
2431 * @param {number} min
2432 * @param {number} max
2433 * @param {number}
2434 */
2435function getRandomNum(min, max) {
2436 return Math.floor(Math.random() * (max - min + 1)) + min;
2437}
2438
2439
2440/**
2441 * Vibrate on mobile devices.
2442 * @param {number} duration Duration of the vibration in milliseconds.
2443 */
2444function vibrate(duration) {
2445 if (IS_MOBILE && window.navigator.vibrate) {
2446 window.navigator.vibrate(duration);
2447 }
2448}
2449
2450
2451/**
2452 * Create canvas element.
2453 * @param {HTMLElement} container Element to append canvas to.
2454 * @param {number} width
2455 * @param {number} height
2456 * @param {string} opt_classname
2457 * @return {HTMLCanvasElement}
2458 */
2459function createCanvas(container, width, height, opt_classname) {
2460 var canvas = document.createElement('canvas');
2461 canvas.className = opt_classname ? Runner.classes.CANVAS + ' ' +
2462 opt_classname : Runner.classes.CANVAS;
2463 canvas.width = width;
2464 canvas.height = height;
2465 container.appendChild(canvas);
2466
2467 return canvas;
2468}
2469
2470
2471/**
2472 * Decodes the base 64 audio to ArrayBuffer used by Web Audio.
2473 * @param {string} base64String
2474 */
2475function decodeBase64ToArrayBuffer(base64String) {
2476 var len = (base64String.length / 4) * 3;
2477 var str = atob(base64String);
2478 var arrayBuffer = new ArrayBuffer(len);
2479 var bytes = new Uint8Array(arrayBuffer);
2480
2481 for (var i = 0; i < len; i++) {
2482 bytes[i] = str.charCodeAt(i);
2483 }
2484 return bytes.buffer;
2485}
2486
2487
2488/**
2489 * Return the current timestamp.
2490 * @return {number}
2491 */
2492function getTimeStamp() {
2493 return IS_IOS ? new Date().getTime() : performance.now();
2494}
2495
2496
2497//******************************************************************************
2498
2499
2500/**
2501 * Game over panel.
2502 * @param {!HTMLCanvasElement} canvas
2503 * @param {Object} textImgPos
2504 * @param {Object} restartImgPos
2505 * @param {!Object} dimensions Canvas dimensions.
2506 * @constructor
2507 */
2508function GameOverPanel(canvas, textImgPos, restartImgPos, dimensions) {
2509 this.canvas = canvas;
2510 this.canvasCtx = canvas.getContext('2d');
2511 this.canvasDimensions = dimensions;
2512 this.textImgPos = textImgPos;
2513 this.restartImgPos = restartImgPos;
2514 this.draw();
2515};
2516
2517
2518/**
2519 * Dimensions used in the panel.
2520 * @enum {number}
2521 */
2522GameOverPanel.dimensions = {
2523 TEXT_X: 0,
2524 TEXT_Y: 13,
2525 TEXT_WIDTH: 191,
2526 TEXT_HEIGHT: 11,
2527 RESTART_WIDTH: 36,
2528 RESTART_HEIGHT: 32
2529};
2530
2531
2532GameOverPanel.prototype = {
2533 /**
2534 * Update the panel dimensions.
2535 * @param {number} width New canvas width.
2536 * @param {number} opt_height Optional new canvas height.
2537 */
2538 updateDimensions: function(width, opt_height) {
2539 this.canvasDimensions.WIDTH = width;
2540 if (opt_height) {
2541 this.canvasDimensions.HEIGHT = opt_height;
2542 }
2543 },
2544
2545 /**
2546 * Draw the panel.
2547 */
2548 draw: function() {
2549 var dimensions = GameOverPanel.dimensions;
2550
2551 var centerX = this.canvasDimensions.WIDTH / 2;
2552
2553 // Game over text.
2554 var textSourceX = dimensions.TEXT_X;
2555 var textSourceY = dimensions.TEXT_Y;
2556 var textSourceWidth = dimensions.TEXT_WIDTH;
2557 var textSourceHeight = dimensions.TEXT_HEIGHT;
2558
2559 var textTargetX = Math.round(centerX - (dimensions.TEXT_WIDTH / 2));
2560 var textTargetY = Math.round((this.canvasDimensions.HEIGHT - 25) / 3);
2561 var textTargetWidth = dimensions.TEXT_WIDTH;
2562 var textTargetHeight = dimensions.TEXT_HEIGHT;
2563
2564 var restartSourceWidth = dimensions.RESTART_WIDTH;
2565 var restartSourceHeight = dimensions.RESTART_HEIGHT;
2566 var restartTargetX = centerX - (dimensions.RESTART_WIDTH / 2);
2567 var restartTargetY = this.canvasDimensions.HEIGHT / 2;
2568
2569 if (IS_HIDPI) {
2570 textSourceY *= 2;
2571 textSourceX *= 2;
2572 textSourceWidth *= 2;
2573 textSourceHeight *= 2;
2574 restartSourceWidth *= 2;
2575 restartSourceHeight *= 2;
2576 }
2577
2578 textSourceX += this.textImgPos.x;
2579 textSourceY += this.textImgPos.y;
2580
2581 // Game over text from sprite.
2582 this.canvasCtx.drawImage(Runner.imageSprite,
2583 textSourceX, textSourceY, textSourceWidth, textSourceHeight,
2584 textTargetX, textTargetY, textTargetWidth, textTargetHeight);
2585
2586 // Restart button.
2587 this.canvasCtx.drawImage(Runner.imageSprite,
2588 this.restartImgPos.x, this.restartImgPos.y,
2589 restartSourceWidth, restartSourceHeight,
2590 restartTargetX, restartTargetY, dimensions.RESTART_WIDTH,
2591 dimensions.RESTART_HEIGHT);
2592 }
2593};
2594
2595
2596//******************************************************************************
2597
2598/**
2599 * Check for a collision.
2600 * @param {!Obstacle} obstacle
2601 * @param {!Trex} tRex T-rex object.
2602 * @param {HTMLCanvasContext} opt_canvasCtx Optional canvas context for drawing
2603 * collision boxes.
2604 * @return {Array<CollisionBox>}
2605 */
2606function checkForCollision(obstacle, tRex, opt_canvasCtx) {
2607 var obstacleBoxXPos = Runner.defaultDimensions.WIDTH + obstacle.xPos;
2608
2609 // Adjustments are made to the bounding box as there is a 1 pixel white
2610 // border around the t-rex and obstacles.
2611 var tRexBox = new CollisionBox(
2612 tRex.xPos + 1,
2613 tRex.yPos + 1,
2614 tRex.config.WIDTH - 2,
2615 tRex.config.HEIGHT - 2);
2616
2617 var obstacleBox = new CollisionBox(
2618 obstacle.xPos + 1,
2619 obstacle.yPos + 1,
2620 obstacle.typeConfig.width * obstacle.size - 2,
2621 obstacle.typeConfig.height - 2);
2622
2623 // Debug outer box
2624 if (opt_canvasCtx) {
2625 drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox);
2626 }
2627
2628 // Simple outer bounds check.
2629 if (boxCompare(tRexBox, obstacleBox)) {
2630 var collisionBoxes = obstacle.collisionBoxes;
2631 var tRexCollisionBoxes = tRex.ducking ?
2632 Trex.collisionBoxes.DUCKING : Trex.collisionBoxes.RUNNING;
2633
2634 // Detailed axis aligned box check.
2635 for (var t = 0; t < tRexCollisionBoxes.length; t++) {
2636 for (var i = 0; i < collisionBoxes.length; i++) {
2637 // Adjust the box to actual positions.
2638 var adjTrexBox =
2639 createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox);
2640 var adjObstacleBox =
2641 createAdjustedCollisionBox(collisionBoxes[i], obstacleBox);
2642 var crashed = boxCompare(adjTrexBox, adjObstacleBox);
2643
2644 // Draw boxes for debug.
2645 if (opt_canvasCtx) {
2646 drawCollisionBoxes(opt_canvasCtx, adjTrexBox, adjObstacleBox);
2647 }
2648
2649 if (crashed) {
2650 return [adjTrexBox, adjObstacleBox];
2651 }
2652 }
2653 }
2654 }
2655 return false;
2656};
2657
2658
2659/**
2660 * Adjust the collision box.
2661 * @param {!CollisionBox} box The original box.
2662 * @param {!CollisionBox} adjustment Adjustment box.
2663 * @return {CollisionBox} The adjusted collision box object.
2664 */
2665function createAdjustedCollisionBox(box, adjustment) {
2666 return new CollisionBox(
2667 box.x + adjustment.x,
2668 box.y + adjustment.y,
2669 box.width,
2670 box.height);
2671};
2672
2673
2674/**
2675 * Draw the collision boxes for debug.
2676 */
2677function drawCollisionBoxes(canvasCtx, tRexBox, obstacleBox) {
2678 canvasCtx.save();
2679 canvasCtx.strokeStyle = '#f00';
2680 canvasCtx.strokeRect(tRexBox.x, tRexBox.y, tRexBox.width, tRexBox.height);
2681
2682 canvasCtx.strokeStyle = '#0f0';
2683 canvasCtx.strokeRect(obstacleBox.x, obstacleBox.y,
2684 obstacleBox.width, obstacleBox.height);
2685 canvasCtx.restore();
2686};
2687
2688
2689/**
2690 * Compare two collision boxes for a collision.
2691 * @param {CollisionBox} tRexBox
2692 * @param {CollisionBox} obstacleBox
2693 * @return {boolean} Whether the boxes intersected.
2694 */
2695function boxCompare(tRexBox, obstacleBox) {
2696 var crashed = false;
2697 var tRexBoxX = tRexBox.x;
2698 var tRexBoxY = tRexBox.y;
2699
2700 var obstacleBoxX = obstacleBox.x;
2701 var obstacleBoxY = obstacleBox.y;
2702
2703 // Axis-Aligned Bounding Box method.
2704 if (tRexBox.x < obstacleBoxX + obstacleBox.width &&
2705 tRexBox.x + tRexBox.width > obstacleBoxX &&
2706 tRexBox.y < obstacleBox.y + obstacleBox.height &&
2707 tRexBox.height + tRexBox.y > obstacleBox.y) {
2708 crashed = true;
2709 }
2710
2711 return crashed;
2712};
2713
2714
2715//******************************************************************************
2716
2717/**
2718 * Collision box object.
2719 * @param {number} x X position.
2720 * @param {number} y Y Position.
2721 * @param {number} w Width.
2722 * @param {number} h Height.
2723 */
2724function CollisionBox(x, y, w, h) {
2725 this.x = x;
2726 this.y = y;
2727 this.width = w;
2728 this.height = h;
2729};
2730
2731
2732//******************************************************************************
2733
2734/**
2735 * Obstacle.
2736 * @param {HTMLCanvasCtx} canvasCtx
2737 * @param {Obstacle.type} type
2738 * @param {Object} spritePos Obstacle position in sprite.
2739 * @param {Object} dimensions
2740 * @param {number} gapCoefficient Mutipler in determining the gap.
2741 * @param {number} speed
2742 * @param {number} opt_xOffset
2743 */
2744function Obstacle(canvasCtx, type, spriteImgPos, dimensions,
2745 gapCoefficient, speed, opt_xOffset) {
2746
2747 this.canvasCtx = canvasCtx;
2748 this.spritePos = spriteImgPos;
2749 this.typeConfig = type;
2750 this.gapCoefficient = gapCoefficient;
2751 this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH);
2752 this.dimensions = dimensions;
2753 this.remove = false;
2754 this.xPos = dimensions.WIDTH + (opt_xOffset || 0);
2755 this.yPos = 0;
2756 this.width = 0;
2757 this.collisionBoxes = [];
2758 this.gap = 0;
2759 this.speedOffset = 0;
2760
2761 // For animated obstacles.
2762 this.currentFrame = 0;
2763 this.timer = 0;
2764
2765 this.init(speed);
2766};
2767
2768/**
2769 * Coefficient for calculating the maximum gap.
2770 * @const
2771 */
2772Obstacle.MAX_GAP_COEFFICIENT = 1.5;
2773
2774/**
2775 * Maximum obstacle grouping count.
2776 * @const
2777 */
2778Obstacle.MAX_OBSTACLE_LENGTH = 3,
2779
2780
2781Obstacle.prototype = {
2782 /**
2783 * Initialise the DOM for the obstacle.
2784 * @param {number} speed
2785 */
2786 init: function(speed) {
2787 this.cloneCollisionBoxes();
2788
2789 // Only allow sizing if we're at the right speed.
2790 if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {
2791 this.size = 1;
2792 }
2793
2794 this.width = this.typeConfig.width * this.size;
2795
2796 // Check if obstacle can be positioned at various heights.
2797 if (Array.isArray(this.typeConfig.yPos)) {
2798 var yPosConfig = IS_MOBILE ? this.typeConfig.yPosMobile :
2799 this.typeConfig.yPos;
2800 this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)];
2801 } else {
2802 this.yPos = this.typeConfig.yPos;
2803 }
2804
2805 this.draw();
2806
2807 // Make collision box adjustments,
2808 // Central box is adjusted to the size as one box.
2809 // ____ ______ ________
2810 // _| |-| _| |-| _| |-|
2811 // | |<->| | | |<--->| | | |<----->| |
2812 // | | 1 | | | | 2 | | | | 3 | |
2813 // |_|___|_| |_|_____|_| |_|_______|_|
2814 //
2815 if (this.size > 1) {
2816 this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width -
2817 this.collisionBoxes[2].width;
2818 this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width;
2819 }
2820
2821 // For obstacles that go at a different speed from the horizon.
2822 if (this.typeConfig.speedOffset) {
2823 this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset :
2824 -this.typeConfig.speedOffset;
2825 }
2826
2827 this.gap = this.getGap(this.gapCoefficient, speed);
2828 },
2829
2830 /**
2831 * Draw and crop based on size.
2832 */
2833 draw: function() {
2834 var sourceWidth = this.typeConfig.width;
2835 var sourceHeight = this.typeConfig.height;
2836
2837 if (IS_HIDPI) {
2838 sourceWidth = sourceWidth * 2;
2839 sourceHeight = sourceHeight * 2;
2840 }
2841
2842 // X position in sprite.
2843 var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) +
2844 this.spritePos.x;
2845
2846 // Animation frames.
2847 if (this.currentFrame > 0) {
2848 sourceX += sourceWidth * this.currentFrame;
2849 }
2850
2851 this.canvasCtx.drawImage(Runner.imageSprite,
2852 sourceX, this.spritePos.y,
2853 sourceWidth * this.size, sourceHeight,
2854 this.xPos, this.yPos,
2855 this.typeConfig.width * this.size, this.typeConfig.height);
2856 },
2857
2858 /**
2859 * Obstacle frame update.
2860 * @param {number} deltaTime
2861 * @param {number} speed
2862 */
2863 update: function(deltaTime, speed) {
2864 if (!this.remove) {
2865 if (this.typeConfig.speedOffset) {
2866 speed += this.speedOffset;
2867 }
2868 this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime);
2869
2870 // Update frame
2871 if (this.typeConfig.numFrames) {
2872 this.timer += deltaTime;
2873 if (this.timer >= this.typeConfig.frameRate) {
2874 this.currentFrame =
2875 this.currentFrame == this.typeConfig.numFrames - 1 ?
2876 0 : this.currentFrame + 1;
2877 this.timer = 0;
2878 }
2879 }
2880 this.draw();
2881
2882 if (!this.isVisible()) {
2883 this.remove = true;
2884 }
2885 }
2886 },
2887
2888 /**
2889 * Calculate a random gap size.
2890 * - Minimum gap gets wider as speed increses
2891 * @param {number} gapCoefficient
2892 * @param {number} speed
2893 * @return {number} The gap size.
2894 */
2895 getGap: function(gapCoefficient, speed) {
2896 var minGap = Math.round(this.width * speed +
2897 this.typeConfig.minGap * gapCoefficient);
2898 var maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT);
2899 return getRandomNum(minGap, maxGap);
2900 },
2901
2902 /**
2903 * Check if obstacle is visible.
2904 * @return {boolean} Whether the obstacle is in the game area.
2905 */
2906 isVisible: function() {
2907 return this.xPos + this.width > 0;
2908 },
2909
2910 /**
2911 * Make a copy of the collision boxes, since these will change based on
2912 * obstacle type and size.
2913 */
2914 cloneCollisionBoxes: function() {
2915 var collisionBoxes = this.typeConfig.collisionBoxes;
2916
2917 for (var i = collisionBoxes.length - 1; i >= 0; i--) {
2918 this.collisionBoxes[i] = new CollisionBox(collisionBoxes[i].x,
2919 collisionBoxes[i].y, collisionBoxes[i].width,
2920 collisionBoxes[i].height);
2921 }
2922 }
2923};
2924
2925
2926/**
2927 * Obstacle definitions.
2928 * minGap: minimum pixel space betweeen obstacles.
2929 * multipleSpeed: Speed at which multiples are allowed.
2930 * speedOffset: speed faster / slower than the horizon.
2931 * minSpeed: Minimum speed which the obstacle can make an appearance.
2932 */
2933Obstacle.types = [
2934 {
2935 type: 'CACTUS_SMALL',
2936 width: 17,
2937 height: 35,
2938 yPos: 105,
2939 multipleSpeed: 4,
2940 minGap: 120,
2941 minSpeed: 0,
2942 collisionBoxes: [
2943 new CollisionBox(0, 7, 5, 27),
2944 new CollisionBox(4, 0, 6, 34),
2945 new CollisionBox(10, 4, 7, 14)
2946 ]
2947 },
2948 {
2949 type: 'CACTUS_LARGE',
2950 width: 25,
2951 height: 50,
2952 yPos: 90,
2953 multipleSpeed: 7,
2954 minGap: 120,
2955 minSpeed: 0,
2956 collisionBoxes: [
2957 new CollisionBox(0, 12, 7, 38),
2958 new CollisionBox(8, 0, 7, 49),
2959 new CollisionBox(13, 10, 10, 38)
2960 ]
2961 },
2962 {
2963 type: 'PTERODACTYL',
2964 width: 46,
2965 height: 40,
2966 yPos: [ 100, 75, 50 ], // Variable height.
2967 yPosMobile: [ 100, 50 ], // Variable height mobile.
2968 multipleSpeed: 999,
2969 minSpeed: 8.5,
2970 minGap: 150,
2971 collisionBoxes: [
2972 new CollisionBox(15, 15, 16, 5),
2973 new CollisionBox(18, 21, 24, 6),
2974 new CollisionBox(2, 14, 4, 3),
2975 new CollisionBox(6, 10, 4, 7),
2976 new CollisionBox(10, 8, 6, 9)
2977 ],
2978 numFrames: 2,
2979 frameRate: 1000/6,
2980 speedOffset: .8
2981 }
2982];
2983
2984
2985//******************************************************************************
2986/**
2987 * T-rex game character.
2988 * @param {HTMLCanvas} canvas
2989 * @param {Object} spritePos Positioning within image sprite.
2990 * @constructor
2991 */
2992function Trex(canvas, spritePos) {
2993 this.canvas = canvas;
2994 this.canvasCtx = canvas.getContext('2d');
2995 this.spritePos = spritePos;
2996 this.xPos = 0;
2997 this.yPos = 0;
2998 // Position when on the ground.
2999 this.groundYPos = 0;
3000 this.currentFrame = 0;
3001 this.currentAnimFrames = [];
3002 this.blinkDelay = 0;
3003 this.blinkCount = 0;
3004 this.animStartTime = 0;
3005 this.timer = 0;
3006 this.msPerFrame = 1000 / FPS;
3007 this.config = Trex.config;
3008 // Current status.
3009 this.status = Trex.status.WAITING;
3010
3011 this.jumping = false;
3012 this.ducking = false;
3013 this.jumpVelocity = 0;
3014 this.reachedMinHeight = false;
3015 this.speedDrop = false;
3016 this.jumpCount = 0;
3017 this.jumpspotX = 0;
3018
3019 this.init();
3020};
3021
3022
3023/**
3024 * T-rex player config.
3025 * @enum {number}
3026 */
3027Trex.config = {
3028 DROP_VELOCITY: -5,
3029 GRAVITY: 0.6,
3030 HEIGHT: 47,
3031 HEIGHT_DUCK: 25,
3032 INIITAL_JUMP_VELOCITY: -10,
3033 INTRO_DURATION: 1500,
3034 MAX_JUMP_HEIGHT: 30,
3035 MIN_JUMP_HEIGHT: 30,
3036 SPEED_DROP_COEFFICIENT: 3,
3037 SPRITE_WIDTH: 262,
3038 START_X_POS: 50,
3039 WIDTH: 44,
3040 WIDTH_DUCK: 59
3041};
3042
3043
3044/**
3045 * Used in collision detection.
3046 * @type {Array<CollisionBox>}
3047 */
3048Trex.collisionBoxes = {
3049 DUCKING: [
3050 new CollisionBox(1, 18, 55, 25)
3051 ],
3052 RUNNING: [
3053 new CollisionBox(22, 0, 17, 16),
3054 new CollisionBox(1, 18, 30, 9),
3055 new CollisionBox(10, 35, 14, 8),
3056 new CollisionBox(1, 24, 29, 5),
3057 new CollisionBox(5, 30, 21, 4),
3058 new CollisionBox(9, 34, 15, 4)
3059 ]
3060};
3061
3062
3063/**
3064 * Animation states.
3065 * @enum {string}
3066 */
3067Trex.status = {
3068 CRASHED: 'CRASHED',
3069 DUCKING: 'DUCKING',
3070 JUMPING: 'JUMPING',
3071 RUNNING: 'RUNNING',
3072 WAITING: 'WAITING'
3073};
3074
3075/**
3076 * Blinking coefficient.
3077 * @const
3078 */
3079Trex.BLINK_TIMING = 7000;
3080
3081
3082/**
3083 * Animation config for different states.
3084 * @enum {Object}
3085 */
3086Trex.animFrames = {
3087 WAITING: {
3088 frames: [44, 0],
3089 msPerFrame: 1000 / 3
3090 },
3091 RUNNING: {
3092 frames: [88, 132],
3093 msPerFrame: 1000 / 12
3094 },
3095 CRASHED: {
3096 frames: [220],
3097 msPerFrame: 1000 / 60
3098 },
3099 JUMPING: {
3100 frames: [0],
3101 msPerFrame: 1000 / 60
3102 },
3103 DUCKING: {
3104 frames: [262, 321],
3105 msPerFrame: 1000 / 8
3106 }
3107};
3108
3109
3110Trex.prototype = {
3111 /**
3112 * T-rex player initaliser.
3113 * Sets the t-rex to blink at random intervals.
3114 */
3115 init: function() {
3116 this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT -
3117 Runner.config.BOTTOM_PAD;
3118 this.yPos = this.groundYPos;
3119 this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT;
3120
3121 this.draw(0, 0);
3122 this.update(0, Trex.status.WAITING);
3123 },
3124
3125 /**
3126 * Setter for the jump velocity.
3127 * The approriate drop velocity is also set.
3128 */
3129 setJumpVelocity: function(setting) {
3130 this.config.INIITAL_JUMP_VELOCITY = -setting;
3131 this.config.DROP_VELOCITY = -setting / 2;
3132 },
3133
3134 /**
3135 * Set the animation status.
3136 * @param {!number} deltaTime
3137 * @param {Trex.status} status Optional status to switch to.
3138 */
3139 update: function(deltaTime, opt_status) {
3140 this.timer += deltaTime;
3141
3142 // Update the status.
3143 if (opt_status) {
3144 this.status = opt_status;
3145 this.currentFrame = 0;
3146 this.msPerFrame = Trex.animFrames[opt_status].msPerFrame;
3147 this.currentAnimFrames = Trex.animFrames[opt_status].frames;
3148
3149 if (opt_status == Trex.status.WAITING) {
3150 this.animStartTime = getTimeStamp();
3151 this.setBlinkDelay();
3152 }
3153 }
3154
3155 // Game intro animation, T-rex moves in from the left.
3156 if (this.playingIntro && this.xPos < this.config.START_X_POS) {
3157 this.xPos += Math.round((this.config.START_X_POS /
3158 this.config.INTRO_DURATION) * deltaTime);
3159 }
3160
3161 if (this.status == Trex.status.WAITING) {
3162 this.blink(getTimeStamp());
3163 } else {
3164 this.draw(this.currentAnimFrames[this.currentFrame], 0);
3165 }
3166
3167 // Update the frame position.
3168 if (this.timer >= this.msPerFrame) {
3169 this.currentFrame = this.currentFrame ==
3170 this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1;
3171 this.timer = 0;
3172 }
3173
3174 // Speed drop becomes duck if the down key is still being pressed.
3175 if (this.speedDrop && this.yPos == this.groundYPos) {
3176 this.speedDrop = false;
3177 this.setDuck(true);
3178 }
3179 },
3180
3181 /**
3182 * Draw the t-rex to a particular position.
3183 * @param {number} x
3184 * @param {number} y
3185 */
3186 draw: function(x, y) {
3187 var sourceX = x;
3188 var sourceY = y;
3189 var sourceWidth = this.ducking && this.status != Trex.status.CRASHED ?
3190 this.config.WIDTH_DUCK : this.config.WIDTH;
3191 var sourceHeight = this.config.HEIGHT;
3192
3193 if (IS_HIDPI) {
3194 sourceX *= 2;
3195 sourceY *= 2;
3196 sourceWidth *= 2;
3197 sourceHeight *= 2;
3198 }
3199
3200 // Adjustments for sprite sheet position.
3201 sourceX += this.spritePos.x;
3202 sourceY += this.spritePos.y;
3203
3204 // Ducking.
3205 if (this.ducking && this.status != Trex.status.CRASHED) {
3206 this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY,
3207 sourceWidth, sourceHeight,
3208 this.xPos, this.yPos,
3209 this.config.WIDTH_DUCK, this.config.HEIGHT);
3210 } else {
3211 // Crashed whilst ducking. Trex is standing up so needs adjustment.
3212 if (this.ducking && this.status == Trex.status.CRASHED) {
3213 this.xPos++;
3214 }
3215 // Standing / running
3216 this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY,
3217 sourceWidth, sourceHeight,
3218 this.xPos, this.yPos,
3219 this.config.WIDTH, this.config.HEIGHT);
3220 }
3221 },
3222
3223 /**
3224 * Sets a random time for the blink to happen.
3225 */
3226 setBlinkDelay: function() {
3227 this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING);
3228 },
3229
3230 /**
3231 * Make t-rex blink at random intervals.
3232 * @param {number} time Current time in milliseconds.
3233 */
3234 blink: function(time) {
3235 var deltaTime = time - this.animStartTime;
3236
3237 if (deltaTime >= this.blinkDelay) {
3238 this.draw(this.currentAnimFrames[this.currentFrame], 0);
3239
3240 if (this.currentFrame == 1) {
3241 // Set new random delay to blink.
3242 this.setBlinkDelay();
3243 this.animStartTime = time;
3244 this.blinkCount++;
3245 }
3246 }
3247 },
3248
3249 /**
3250 * Initialise a jump.
3251 * @param {number} speed
3252 */
3253 startJump: function(speed) {
3254 if (!this.jumping) {
3255 this.update(0, Trex.status.JUMPING);
3256 // Tweak the jump velocity based on the speed.
3257 this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY - (speed / 10);
3258 this.jumping = true;
3259 this.reachedMinHeight = false;
3260 this.speedDrop = false;
3261 }
3262 },
3263
3264 /**
3265 * Jump is complete, falling down.
3266 */
3267 endJump: function() {
3268 if (this.reachedMinHeight &&
3269 this.jumpVelocity < this.config.DROP_VELOCITY) {
3270 this.jumpVelocity = this.config.DROP_VELOCITY;
3271 }
3272 },
3273
3274 /**
3275 * Update frame for a jump.
3276 * @param {number} deltaTime
3277 * @param {number} speed
3278 */
3279 updateJump: function(deltaTime, speed) {
3280 var msPerFrame = Trex.animFrames[this.status].msPerFrame;
3281 var framesElapsed = deltaTime / msPerFrame;
3282
3283 // Speed drop makes Trex fall faster.
3284 if (this.speedDrop) {
3285 this.yPos += Math.round(this.jumpVelocity *
3286 this.config.SPEED_DROP_COEFFICIENT * framesElapsed);
3287 } else {
3288 this.yPos += Math.round(this.jumpVelocity * framesElapsed);
3289 }
3290
3291 this.jumpVelocity += this.config.GRAVITY * framesElapsed;
3292
3293 // Minimum height has been reached.
3294 if (this.yPos < this.minJumpHeight || this.speedDrop) {
3295 this.reachedMinHeight = true;
3296 }
3297
3298 // Reached max height
3299 if (this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop) {
3300 this.endJump();
3301 }
3302
3303 // Back down at ground level. Jump completed.
3304 if (this.yPos > this.groundYPos) {
3305 this.reset();
3306 this.jumpCount++;
3307 }
3308
3309 this.update(deltaTime);
3310 },
3311
3312 /**
3313 * Set the speed drop. Immediately cancels the current jump.
3314 */
3315 setSpeedDrop: function() {
3316 this.speedDrop = true;
3317 this.jumpVelocity = 1;
3318 },
3319
3320 /**
3321 * @param {boolean} isDucking.
3322 */
3323 setDuck: function(isDucking) {
3324 if (isDucking && this.status != Trex.status.DUCKING) {
3325 this.update(0, Trex.status.DUCKING);
3326 this.ducking = true;
3327 } else if (this.status == Trex.status.DUCKING) {
3328 this.update(0, Trex.status.RUNNING);
3329 this.ducking = false;
3330 }
3331 },
3332
3333 /**
3334 * Reset the t-rex to running at start of game.
3335 */
3336 reset: function() {
3337 this.yPos = this.groundYPos;
3338 this.jumpVelocity = 0;
3339 this.jumping = false;
3340 this.ducking = false;
3341 this.update(0, Trex.status.RUNNING);
3342 this.midair = false;
3343 this.speedDrop = false;
3344 this.jumpCount = 0;
3345 }
3346};
3347
3348
3349//******************************************************************************
3350
3351/**
3352 * Handles displaying the distance meter.
3353 * @param {!HTMLCanvasElement} canvas
3354 * @param {Object} spritePos Image position in sprite.
3355 * @param {number} canvasWidth
3356 * @constructor
3357 */
3358function DistanceMeter(canvas, spritePos, canvasWidth) {
3359 this.canvas = canvas;
3360 this.canvasCtx = canvas.getContext('2d');
3361 this.image = Runner.imageSprite;
3362 this.spritePos = spritePos;
3363 this.x = 0;
3364 this.y = 5;
3365
3366 this.currentDistance = 0;
3367 this.maxScore = 0;
3368 this.highScore = 0;
3369 this.container = null;
3370
3371 this.digits = [];
3372 this.achievement = false;
3373 this.defaultString = '';
3374 this.flashTimer = 0;
3375 this.flashIterations = 0;
3376 this.invertTrigger = false;
3377
3378 this.config = DistanceMeter.config;
3379 this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS;
3380 this.init(canvasWidth);
3381};
3382
3383
3384/**
3385 * @enum {number}
3386 */
3387DistanceMeter.dimensions = {
3388 WIDTH: 10,
3389 HEIGHT: 13,
3390 DEST_WIDTH: 11
3391};
3392
3393
3394/**
3395 * Y positioning of the digits in the sprite sheet.
3396 * X position is always 0.
3397 * @type {Array<number>}
3398 */
3399DistanceMeter.yPos = [0, 13, 27, 40, 53, 67, 80, 93, 107, 120];
3400
3401
3402/**
3403 * Distance meter config.
3404 * @enum {number}
3405 */
3406DistanceMeter.config = {
3407 // Number of digits.
3408 MAX_DISTANCE_UNITS: 5,
3409
3410 // Distance that causes achievement animation.
3411 ACHIEVEMENT_DISTANCE: 100,
3412
3413 // Used for conversion from pixel distance to a scaled unit.
3414 COEFFICIENT: 0.025,
3415
3416 // Flash duration in milliseconds.
3417 FLASH_DURATION: 1000 / 4,
3418
3419 // Flash iterations for achievement animation.
3420 FLASH_ITERATIONS: 3
3421};
3422
3423
3424DistanceMeter.prototype = {
3425 /**
3426 * Initialise the distance meter to '00000'.
3427 * @param {number} width Canvas width in px.
3428 */
3429 init: function(width) {
3430 var maxDistanceStr = '';
3431
3432 this.calcXPos(width);
3433 this.maxScore = this.maxScoreUnits;
3434 for (var i = 0; i < this.maxScoreUnits; i++) {
3435 this.draw(i, 0);
3436 this.defaultString += '0';
3437 maxDistanceStr += '9';
3438 }
3439
3440 this.maxScore = parseInt(maxDistanceStr);
3441 },
3442
3443 /**
3444 * Calculate the xPos in the canvas.
3445 * @param {number} canvasWidth
3446 */
3447 calcXPos: function(canvasWidth) {
3448 this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH *
3449 (this.maxScoreUnits + 1));
3450 },
3451
3452 /**
3453 * Draw a digit to canvas.
3454 * @param {number} digitPos Position of the digit.
3455 * @param {number} value Digit value 0-9.
3456 * @param {boolean} opt_highScore Whether drawing the high score.
3457 */
3458 draw: function(digitPos, value, opt_highScore) {
3459 var sourceWidth = DistanceMeter.dimensions.WIDTH;
3460 var sourceHeight = DistanceMeter.dimensions.HEIGHT;
3461 var sourceX = DistanceMeter.dimensions.WIDTH * value;
3462 var sourceY = 0;
3463
3464 var targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH;
3465 var targetY = this.y;
3466 var targetWidth = DistanceMeter.dimensions.WIDTH;
3467 var targetHeight = DistanceMeter.dimensions.HEIGHT;
3468
3469 // For high DPI we 2x source values.
3470 if (IS_HIDPI) {
3471 sourceWidth *= 2;
3472 sourceHeight *= 2;
3473 sourceX *= 2;
3474 }
3475
3476 sourceX += this.spritePos.x;
3477 sourceY += this.spritePos.y;
3478
3479 this.canvasCtx.save();
3480
3481 if (opt_highScore) {
3482 // Left of the current score.
3483 var highScoreX = this.x - (this.maxScoreUnits * 2) *
3484 DistanceMeter.dimensions.WIDTH;
3485 this.canvasCtx.translate(highScoreX, this.y);
3486 } else {
3487 this.canvasCtx.translate(this.x, this.y);
3488 }
3489
3490 this.canvasCtx.drawImage(this.image, sourceX, sourceY,
3491 sourceWidth, sourceHeight,
3492 targetX, targetY,
3493 targetWidth, targetHeight
3494 );
3495
3496 this.canvasCtx.restore();
3497 },
3498
3499 /**
3500 * Covert pixel distance to a 'real' distance.
3501 * @param {number} distance Pixel distance ran.
3502 * @return {number} The 'real' distance ran.
3503 */
3504 getActualDistance: function(distance) {
3505 return distance ? Math.round(distance * this.config.COEFFICIENT) : 0;
3506 },
3507
3508 /**
3509 * Update the distance meter.
3510 * @param {number} distance
3511 * @param {number} deltaTime
3512 * @return {boolean} Whether the acheivement sound fx should be played.
3513 */
3514 update: function(deltaTime, distance) {
3515 var paint = true;
3516 var playSound = false;
3517
3518 if (!this.achievement) {
3519 distance = this.getActualDistance(distance);
3520 // Score has gone beyond the initial digit count.
3521 if (distance > this.maxScore && this.maxScoreUnits ==
3522 this.config.MAX_DISTANCE_UNITS) {
3523 this.maxScoreUnits++;
3524 this.maxScore = parseInt(this.maxScore + '9');
3525 } else {
3526 this.distance = 0;
3527 }
3528
3529 if (distance > 0) {
3530 // Acheivement unlocked
3531 if (distance % this.config.ACHIEVEMENT_DISTANCE == 0) {
3532 // Flash score and play sound.
3533 this.achievement = true;
3534 this.flashTimer = 0;
3535 playSound = true;
3536 }
3537
3538 // Create a string representation of the distance with leading 0.
3539 var distanceStr = (this.defaultString +
3540 distance).substr(-this.maxScoreUnits);
3541 this.digits = distanceStr.split('');
3542 } else {
3543 this.digits = this.defaultString.split('');
3544 }
3545 } else {
3546 // Control flashing of the score on reaching acheivement.
3547 if (this.flashIterations <= this.config.FLASH_ITERATIONS) {
3548 this.flashTimer += deltaTime;
3549
3550 if (this.flashTimer < this.config.FLASH_DURATION) {
3551 paint = false;
3552 } else if (this.flashTimer >
3553 this.config.FLASH_DURATION * 2) {
3554 this.flashTimer = 0;
3555 this.flashIterations++;
3556 }
3557 } else {
3558 this.achievement = false;
3559 this.flashIterations = 0;
3560 this.flashTimer = 0;
3561 }
3562 }
3563
3564 // Draw the digits if not flashing.
3565 if (paint) {
3566 for (var i = this.digits.length - 1; i >= 0; i--) {
3567 this.draw(i, parseInt(this.digits[i]));
3568 }
3569 }
3570
3571 this.drawHighScore();
3572 return playSound;
3573 },
3574
3575 /**
3576 * Draw the high score.
3577 */
3578 drawHighScore: function() {
3579 this.canvasCtx.save();
3580 this.canvasCtx.globalAlpha = .8;
3581 for (var i = this.highScore.length - 1; i >= 0; i--) {
3582 this.draw(i, parseInt(this.highScore[i], 10), true);
3583 }
3584 this.canvasCtx.restore();
3585 },
3586
3587 /**
3588 * Set the highscore as a array string.
3589 * Position of char in the sprite: H - 10, I - 11.
3590 * @param {number} distance Distance ran in pixels.
3591 */
3592 setHighScore: function(distance) {
3593 distance = this.getActualDistance(distance);
3594 var highScoreStr = (this.defaultString +
3595 distance).substr(-this.maxScoreUnits);
3596
3597 this.highScore = ['10', '11', ''].concat(highScoreStr.split(''));
3598 },
3599
3600 /**
3601 * Reset the distance meter back to '00000'.
3602 */
3603 reset: function() {
3604 this.update(0);
3605 this.achievement = false;
3606 }
3607};
3608
3609
3610//******************************************************************************
3611
3612/**
3613 * Cloud background item.
3614 * Similar to an obstacle object but without collision boxes.
3615 * @param {HTMLCanvasElement} canvas Canvas element.
3616 * @param {Object} spritePos Position of image in sprite.
3617 * @param {number} containerWidth
3618 */
3619function Cloud(canvas, spritePos, containerWidth) {
3620 this.canvas = canvas;
3621 this.canvasCtx = this.canvas.getContext('2d');
3622 this.spritePos = spritePos;
3623 this.containerWidth = containerWidth;
3624 this.xPos = containerWidth;
3625 this.yPos = 0;
3626 this.remove = false;
3627 this.cloudGap = getRandomNum(Cloud.config.MIN_CLOUD_GAP,
3628 Cloud.config.MAX_CLOUD_GAP);
3629
3630 this.init();
3631};
3632
3633
3634/**
3635 * Cloud object config.
3636 * @enum {number}
3637 */
3638Cloud.config = {
3639 HEIGHT: 14,
3640 MAX_CLOUD_GAP: 400,
3641 MAX_SKY_LEVEL: 30,
3642 MIN_CLOUD_GAP: 100,
3643 MIN_SKY_LEVEL: 71,
3644 WIDTH: 46
3645};
3646
3647
3648Cloud.prototype = {
3649 /**
3650 * Initialise the cloud. Sets the Cloud height.
3651 */
3652 init: function() {
3653 this.yPos = getRandomNum(Cloud.config.MAX_SKY_LEVEL,
3654 Cloud.config.MIN_SKY_LEVEL);
3655 this.draw();
3656 },
3657
3658 /**
3659 * Draw the cloud.
3660 */
3661 draw: function() {
3662 this.canvasCtx.save();
3663 var sourceWidth = Cloud.config.WIDTH;
3664 var sourceHeight = Cloud.config.HEIGHT;
3665
3666 if (IS_HIDPI) {
3667 sourceWidth = sourceWidth * 2;
3668 sourceHeight = sourceHeight * 2;
3669 }
3670
3671 this.canvasCtx.drawImage(Runner.imageSprite, this.spritePos.x,
3672 this.spritePos.y,
3673 sourceWidth, sourceHeight,
3674 this.xPos, this.yPos,
3675 Cloud.config.WIDTH, Cloud.config.HEIGHT);
3676
3677 this.canvasCtx.restore();
3678 },
3679
3680 /**
3681 * Update the cloud position.
3682 * @param {number} speed
3683 */
3684 update: function(speed) {
3685 if (!this.remove) {
3686 this.xPos -= Math.ceil(speed);
3687 this.draw();
3688
3689 // Mark as removeable if no longer in the canvas.
3690 if (!this.isVisible()) {
3691 this.remove = true;
3692 }
3693 }
3694 },
3695
3696 /**
3697 * Check if the cloud is visible on the stage.
3698 * @return {boolean}
3699 */
3700 isVisible: function() {
3701 return this.xPos + Cloud.config.WIDTH > 0;
3702 }
3703};
3704
3705
3706//******************************************************************************
3707
3708/**
3709 * Nightmode shows a moon and stars on the horizon.
3710 */
3711function NightMode(canvas, spritePos, containerWidth) {
3712 this.spritePos = spritePos;
3713 this.canvas = canvas;
3714 this.canvasCtx = canvas.getContext('2d');
3715 this.xPos = containerWidth - 50;
3716 this.yPos = 30;
3717 this.currentPhase = 0;
3718 this.opacity = 0;
3719 this.containerWidth = containerWidth;
3720 this.stars = [];
3721 this.drawStars = false;
3722 this.placeStars();
3723};
3724
3725/**
3726 * @enum {number}
3727 */
3728NightMode.config = {
3729 FADE_SPEED: 0.035,
3730 HEIGHT: 40,
3731 MOON_SPEED: 0.25,
3732 NUM_STARS: 2,
3733 STAR_SIZE: 9,
3734 STAR_SPEED: 0.3,
3735 STAR_MAX_Y: 70,
3736 WIDTH: 20
3737};
3738
3739NightMode.phases = [140, 120, 100, 60, 40, 20, 0];
3740
3741NightMode.prototype = {
3742 /**
3743 * Update moving moon, changing phases.
3744 * @param {boolean} activated Whether night mode is activated.
3745 * @param {number} delta
3746 */
3747 update: function(activated, delta) {
3748 // Moon phase.
3749 if (activated && this.opacity == 0) {
3750 this.currentPhase++;
3751
3752 if (this.currentPhase >= NightMode.phases.length) {
3753 this.currentPhase = 0;
3754 }
3755 }
3756
3757 // Fade in / out.
3758 if (activated && (this.opacity < 1 || this.opacity == 0)) {
3759 this.opacity += NightMode.config.FADE_SPEED;
3760 } else if (this.opacity > 0) {
3761 this.opacity -= NightMode.config.FADE_SPEED;
3762 }
3763
3764 // Set moon positioning.
3765 if (this.opacity > 0) {
3766 this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED);
3767
3768 // Update stars.
3769 if (this.drawStars) {
3770 for (var i = 0; i < NightMode.config.NUM_STARS; i++) {
3771 this.stars[i].x = this.updateXPos(this.stars[i].x,
3772 NightMode.config.STAR_SPEED);
3773 }
3774 }
3775 this.draw();
3776 } else {
3777 this.opacity = 0;
3778 this.placeStars();
3779 }
3780 this.drawStars = true;
3781 },
3782
3783 updateXPos: function(currentPos, speed) {
3784 if (currentPos < -NightMode.config.WIDTH) {
3785 currentPos = this.containerWidth;
3786 } else {
3787 currentPos -= speed;
3788 }
3789 return currentPos;
3790 },
3791
3792 draw: function() {
3793 var moonSourceWidth = this.currentPhase == 3 ? NightMode.config.WIDTH * 2 :
3794 NightMode.config.WIDTH;
3795 var moonSourceHeight = NightMode.config.HEIGHT;
3796 var moonSourceX = this.spritePos.x + NightMode.phases[this.currentPhase];
3797 var moonOutputWidth = moonSourceWidth;
3798 var starSize = NightMode.config.STAR_SIZE;
3799 var starSourceX = Runner.spriteDefinition.LDPI.STAR.x;
3800
3801 if (IS_HIDPI) {
3802 moonSourceWidth *= 2;
3803 moonSourceHeight *= 2;
3804 moonSourceX = this.spritePos.x +
3805 (NightMode.phases[this.currentPhase] * 2);
3806 starSize *= 2;
3807 starSourceX = Runner.spriteDefinition.HDPI.STAR.x;
3808 }
3809
3810 this.canvasCtx.save();
3811 this.canvasCtx.globalAlpha = this.opacity;
3812
3813 // Stars.
3814 if (this.drawStars) {
3815 for (var i = 0; i < NightMode.config.NUM_STARS; i++) {
3816 this.canvasCtx.drawImage(Runner.imageSprite,
3817 starSourceX, this.stars[i].sourceY, starSize, starSize,
3818 Math.round(this.stars[i].x), this.stars[i].y,
3819 NightMode.config.STAR_SIZE, NightMode.config.STAR_SIZE);
3820 }
3821 }
3822
3823 // Moon.
3824 this.canvasCtx.drawImage(Runner.imageSprite, moonSourceX,
3825 this.spritePos.y, moonSourceWidth, moonSourceHeight,
3826 Math.round(this.xPos), this.yPos,
3827 moonOutputWidth, NightMode.config.HEIGHT);
3828
3829 this.canvasCtx.globalAlpha = 1;
3830 this.canvasCtx.restore();
3831 },
3832
3833 // Do star placement.
3834 placeStars: function() {
3835 var segmentSize = Math.round(this.containerWidth /
3836 NightMode.config.NUM_STARS);
3837
3838 for (var i = 0; i < NightMode.config.NUM_STARS; i++) {
3839 this.stars[i] = {};
3840 this.stars[i].x = getRandomNum(segmentSize * i, segmentSize * (i + 1));
3841 this.stars[i].y = getRandomNum(0, NightMode.config.STAR_MAX_Y);
3842
3843 if (IS_HIDPI) {
3844 this.stars[i].sourceY = Runner.spriteDefinition.HDPI.STAR.y +
3845 NightMode.config.STAR_SIZE * 2 * i;
3846 } else {
3847 this.stars[i].sourceY = Runner.spriteDefinition.LDPI.STAR.y +
3848 NightMode.config.STAR_SIZE * i;
3849 }
3850 }
3851 },
3852
3853 reset: function() {
3854 this.currentPhase = 0;
3855 this.opacity = 0;
3856 this.update(false);
3857 }
3858
3859};
3860
3861
3862//******************************************************************************
3863
3864/**
3865 * Horizon Line.
3866 * Consists of two connecting lines. Randomly assigns a flat / bumpy horizon.
3867 * @param {HTMLCanvasElement} canvas
3868 * @param {Object} spritePos Horizon position in sprite.
3869 * @constructor
3870 */
3871function HorizonLine(canvas, spritePos) {
3872 this.spritePos = spritePos;
3873 this.canvas = canvas;
3874 this.canvasCtx = canvas.getContext('2d');
3875 this.sourceDimensions = {};
3876 this.dimensions = HorizonLine.dimensions;
3877 this.sourceXPos = [this.spritePos.x, this.spritePos.x +
3878 this.dimensions.WIDTH];
3879 this.xPos = [];
3880 this.yPos = 0;
3881 this.bumpThreshold = 0.5;
3882
3883 this.setSourceDimensions();
3884 this.draw();
3885};
3886
3887
3888/**
3889 * Horizon line dimensions.
3890 * @enum {number}
3891 */
3892HorizonLine.dimensions = {
3893 WIDTH: 600,
3894 HEIGHT: 12,
3895 YPOS: 127
3896};
3897
3898
3899HorizonLine.prototype = {
3900 /**
3901 * Set the source dimensions of the horizon line.
3902 */
3903 setSourceDimensions: function() {
3904
3905 for (var dimension in HorizonLine.dimensions) {
3906 if (IS_HIDPI) {
3907 if (dimension != 'YPOS') {
3908 this.sourceDimensions[dimension] =
3909 HorizonLine.dimensions[dimension] * 2;
3910 }
3911 } else {
3912 this.sourceDimensions[dimension] =
3913 HorizonLine.dimensions[dimension];
3914 }
3915 this.dimensions[dimension] = HorizonLine.dimensions[dimension];
3916 }
3917
3918 this.xPos = [0, HorizonLine.dimensions.WIDTH];
3919 this.yPos = HorizonLine.dimensions.YPOS;
3920 },
3921
3922 /**
3923 * Return the crop x position of a type.
3924 */
3925 getRandomType: function() {
3926 return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0;
3927 },
3928
3929 /**
3930 * Draw the horizon line.
3931 */
3932 draw: function() {
3933 this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[0],
3934 this.spritePos.y,
3935 this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
3936 this.xPos[0], this.yPos,
3937 this.dimensions.WIDTH, this.dimensions.HEIGHT);
3938
3939 this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[1],
3940 this.spritePos.y,
3941 this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
3942 this.xPos[1], this.yPos,
3943 this.dimensions.WIDTH, this.dimensions.HEIGHT);
3944 },
3945
3946 /**
3947 * Update the x position of an indivdual piece of the line.
3948 * @param {number} pos Line position.
3949 * @param {number} increment
3950 */
3951 updateXPos: function(pos, increment) {
3952 var line1 = pos;
3953 var line2 = pos == 0 ? 1 : 0;
3954
3955 this.xPos[line1] -= increment;
3956 this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;
3957
3958 if (this.xPos[line1] <= -this.dimensions.WIDTH) {
3959 this.xPos[line1] += this.dimensions.WIDTH * 2;
3960 this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH;
3961 this.sourceXPos[line1] = this.getRandomType() + this.spritePos.x;
3962 }
3963 },
3964
3965 /**
3966 * Update the horizon line.
3967 * @param {number} deltaTime
3968 * @param {number} speed
3969 */
3970 update: function(deltaTime, speed) {
3971 var increment = Math.floor(speed * (FPS / 1000) * deltaTime);
3972
3973 if (this.xPos[0] <= 0) {
3974 this.updateXPos(0, increment);
3975 } else {
3976 this.updateXPos(1, increment);
3977 }
3978 this.draw();
3979 },
3980
3981 /**
3982 * Reset horizon to the starting position.
3983 */
3984 reset: function() {
3985 this.xPos[0] = 0;
3986 this.xPos[1] = HorizonLine.dimensions.WIDTH;
3987 }
3988};
3989
3990
3991//******************************************************************************
3992
3993/**
3994 * Horizon background class.
3995 * @param {HTMLCanvasElement} canvas
3996 * @param {Object} spritePos Sprite positioning.
3997 * @param {Object} dimensions Canvas dimensions.
3998 * @param {number} gapCoefficient
3999 * @constructor
4000 */
4001function Horizon(canvas, spritePos, dimensions, gapCoefficient) {
4002 this.canvas = canvas;
4003 this.canvasCtx = this.canvas.getContext('2d');
4004 this.config = Horizon.config;
4005 this.dimensions = dimensions;
4006 this.gapCoefficient = gapCoefficient;
4007 this.obstacles = [];
4008 this.obstacleHistory = [];
4009 this.horizonOffsets = [0, 0];
4010 this.cloudFrequency = this.config.CLOUD_FREQUENCY;
4011 this.spritePos = spritePos;
4012 this.nightMode = null;
4013
4014 // Cloud
4015 this.clouds = [];
4016 this.cloudSpeed = this.config.BG_CLOUD_SPEED;
4017
4018 // Horizon
4019 this.horizonLine = null;
4020 this.init();
4021};
4022
4023
4024/**
4025 * Horizon config.
4026 * @enum {number}
4027 */
4028Horizon.config = {
4029 BG_CLOUD_SPEED: 0.2,
4030 BUMPY_THRESHOLD: .3,
4031 CLOUD_FREQUENCY: .5,
4032 HORIZON_HEIGHT: 16,
4033 MAX_CLOUDS: 6
4034};
4035
4036
4037Horizon.prototype = {
4038 /**
4039 * Initialise the horizon. Just add the line and a cloud. No obstacles.
4040 */
4041 init: function() {
4042 this.addCloud();
4043 this.horizonLine = new HorizonLine(this.canvas, this.spritePos.HORIZON);
4044 this.nightMode = new NightMode(this.canvas, this.spritePos.MOON,
4045 this.dimensions.WIDTH);
4046 },
4047
4048 /**
4049 * @param {number} deltaTime
4050 * @param {number} currentSpeed
4051 * @param {boolean} updateObstacles Used as an override to prevent
4052 * the obstacles from being updated / added. This happens in the
4053 * ease in section.
4054 * @param {boolean} showNightMode Night mode activated.
4055 */
4056 update: function(deltaTime, currentSpeed, updateObstacles, showNightMode) {
4057 this.runningTime += deltaTime;
4058 this.horizonLine.update(deltaTime, currentSpeed);
4059 this.nightMode.update(showNightMode);
4060 this.updateClouds(deltaTime, currentSpeed);
4061
4062 if (updateObstacles) {
4063 this.updateObstacles(deltaTime, currentSpeed);
4064 }
4065 },
4066
4067 /**
4068 * Update the cloud positions.
4069 * @param {number} deltaTime
4070 * @param {number} currentSpeed
4071 */
4072 updateClouds: function(deltaTime, speed) {
4073 var cloudSpeed = this.cloudSpeed / 1000 * deltaTime * speed;
4074 var numClouds = this.clouds.length;
4075
4076 if (numClouds) {
4077 for (var i = numClouds - 1; i >= 0; i--) {
4078 this.clouds[i].update(cloudSpeed);
4079 }
4080
4081 var lastCloud = this.clouds[numClouds - 1];
4082
4083 // Check for adding a new cloud.
4084 if (numClouds < this.config.MAX_CLOUDS &&
4085 (this.dimensions.WIDTH - lastCloud.xPos) > lastCloud.cloudGap &&
4086 this.cloudFrequency > Math.random()) {
4087 this.addCloud();
4088 }
4089
4090 // Remove expired clouds.
4091 this.clouds = this.clouds.filter(function(obj) {
4092 return !obj.remove;
4093 });
4094 } else {
4095 this.addCloud();
4096 }
4097 },
4098
4099 /**
4100 * Update the obstacle positions.
4101 * @param {number} deltaTime
4102 * @param {number} currentSpeed
4103 */
4104 updateObstacles: function(deltaTime, currentSpeed) {
4105 // Obstacles, move to Horizon layer.
4106 var updatedObstacles = this.obstacles.slice(0);
4107
4108 for (var i = 0; i < this.obstacles.length; i++) {
4109 var obstacle = this.obstacles[i];
4110 obstacle.update(deltaTime, currentSpeed);
4111
4112 // Clean up existing obstacles.
4113 if (obstacle.remove) {
4114 updatedObstacles.shift();
4115 }
4116 }
4117 this.obstacles = updatedObstacles;
4118
4119 if (this.obstacles.length > 0) {
4120 var lastObstacle = this.obstacles[this.obstacles.length - 1];
4121
4122 if (lastObstacle && !lastObstacle.followingObstacleCreated &&
4123 lastObstacle.isVisible() &&
4124 (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) <
4125 this.dimensions.WIDTH) {
4126 this.addNewObstacle(currentSpeed);
4127 lastObstacle.followingObstacleCreated = true;
4128 }
4129 } else {
4130 // Create new obstacles.
4131 this.addNewObstacle(currentSpeed);
4132 }
4133 },
4134
4135 removeFirstObstacle: function() {
4136 this.obstacles.shift();
4137 },
4138
4139 /**
4140 * Add a new obstacle.
4141 * @param {number} currentSpeed
4142 */
4143 addNewObstacle: function(currentSpeed) {
4144 var obstacleTypeIndex = getRandomNum(0, Obstacle.types.length - 1);
4145 var obstacleType = Obstacle.types[obstacleTypeIndex];
4146
4147 // Check for multiples of the same type of obstacle.
4148 // Also check obstacle is available at current speed.
4149 if (this.duplicateObstacleCheck(obstacleType.type) ||
4150 currentSpeed < obstacleType.minSpeed) {
4151 this.addNewObstacle(currentSpeed);
4152 } else {
4153 var obstacleSpritePos = this.spritePos[obstacleType.type];
4154
4155 this.obstacles.push(new Obstacle(this.canvasCtx, obstacleType,
4156 obstacleSpritePos, this.dimensions,
4157 this.gapCoefficient, currentSpeed, obstacleType.width));
4158
4159 this.obstacleHistory.unshift(obstacleType.type);
4160
4161 if (this.obstacleHistory.length > 1) {
4162 this.obstacleHistory.splice(Runner.config.MAX_OBSTACLE_DUPLICATION);
4163 }
4164 }
4165 },
4166
4167 /**
4168 * Returns whether the previous two obstacles are the same as the next one.
4169 * Maximum duplication is set in config value MAX_OBSTACLE_DUPLICATION.
4170 * @return {boolean}
4171 */
4172 duplicateObstacleCheck: function(nextObstacleType) {
4173 var duplicateCount = 0;
4174
4175 for (var i = 0; i < this.obstacleHistory.length; i++) {
4176 duplicateCount = this.obstacleHistory[i] == nextObstacleType ?
4177 duplicateCount + 1 : 0;
4178 }
4179 return duplicateCount >= Runner.config.MAX_OBSTACLE_DUPLICATION;
4180 },
4181
4182 /**
4183 * Reset the horizon layer.
4184 * Remove existing obstacles and reposition the horizon line.
4185 */
4186 reset: function() {
4187 this.obstacles = [];
4188 this.horizonLine.reset();
4189 this.nightMode.reset();
4190 },
4191
4192 /**
4193 * Update the canvas width and scaling.
4194 * @param {number} width Canvas width.
4195 * @param {number} height Canvas height.
4196 */
4197 resize: function(width, height) {
4198 this.canvas.width = width;
4199 this.canvas.height = height;
4200 },
4201
4202 /**
4203 * Add a new cloud to the horizon.
4204 */
4205 addCloud: function() {
4206 this.clouds.push(new Cloud(this.canvas, this.spritePos.CLOUD,
4207 this.dimensions.WIDTH));
4208 }
4209};
4210})();
4211</script>
4212</head>
4213<body id="t" style="font-family: 'Segoe UI', Tahoma, sans-serif; font-size: 75%" jstcache="0" class="neterror">
4214 <div id="main-frame-error" class="interstitial-wrapper" jstcache="0">
4215 <div id="main-content" jstcache="0">
4216 <div class="icon icon-generic" jseval="updateIconClass(this.classList, iconClass)" alt="" jstcache="1"></div>
4217 <div id="main-message" jstcache="0">
4218 <h1 jsselect="heading" jsvalues=".innerHTML:msg" jstcache="5">Não é possÃvel acessar esse site</h1>
4219 <p jsselect="summary" jsvalues=".innerHTML:msg" jstcache="2">.</p>
4220
4221 <div class="error-code" jscontent="errorCode" jstcache="7">ERR_NAME_NOT_RESOLVED</div>
4222 <div id="diagnose-frame" class="hidden" jstcache="0"></div>
4223 </div>
4224 </div>
4225 <div id="buttons" class="nav-wrapper suggested-left" jstcache="0">
4226 <div id="control-buttons" hidden="" jstcache="0">
4227 <button id="show-saved-copy-button" class="blue-button text-button" onclick="showSavedCopyButtonClick()" jsselect="showSavedCopyButton" jscontent="msg" jsvalues="title:title; .primary:primary" jstcache="9" style="display: none;">
4228 </button><button id="reload-button" class="blue-button text-button" onclick="trackClick(this.trackingId);
4229 reloadButtonClick(this.url);" jsselect="reloadButton" jsvalues=".url:reloadUrl; .trackingId:reloadTrackingId" jscontent="msg" jstcache="8" style="display: none;"></button>
4230
4231 <button id="download-button" class="blue-button text-button" onclick="downloadButtonClick()" jsselect="downloadButton" jscontent="msg" jsvalues=".disabledText:disabledMsg" jstcache="10" style="display: none;">
4232 </button>
4233 </div>
4234 <button id="details-button" class="text-button small-link singular" onclick="detailsButtonClick(); toggleHelpBox()" jscontent="details" jsdisplay="(suggestionsDetails && suggestionsDetails.length > 0) || diagnose" jsvalues=".detailsText:details; .hideDetailsText:hideDetails;" jstcache="3" style="display: none;"></button>
4235 </div>
4236 <div id="details" class="hidden" jstcache="0">
4237 <div class="suggestions" jsselect="suggestionsDetails" jstcache="4" style="display: none;">
4238 <div class="suggestion-header" jsvalues=".innerHTML:header" jstcache="11"></div>
4239 <div class="suggestion-body" jsvalues=".innerHTML:body" jstcache="12"></div>
4240 </div>
4241 </div>
4242 </div>
4243 <div id="sub-frame-error" jstcache="0">
4244 <!-- Show details when hovering over the icon, in case the details are
4245 hidden because they're too large. -->
4246 <div class="icon icon-generic" jseval="updateIconClass(this.classList, iconClass)" jstcache="1"></div>
4247 <div id="sub-frame-error-details" jsselect="summary" jsvalues=".innerHTML:msg" jstcache="2">Não foi possÃvel encontrar o endereço IP do servidor de <strong jscontent="hostName" jstcache="16">www.com.br</strong>.</div>
4248 </div>
4249
4250 <div id="offline-resources" jstcache="0">
4251 <img id="offline-resources-1x" src="data:image/png;base64,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" jstcache="0">
4252 <img id="offline-resources-2x" src="data:image/png;base64,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" jstcache="0">
4253 <template id="audio-resources" jstcache="0">
4254 <audio id="offline-sound-press" src="data:audio/mpeg;base64,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" i18n-processed=""></audio>
4255 <audio id="offline-sound-hit" src="data:audio/mpeg;base64,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" i18n-processed=""></audio>
4256 <audio id="offline-sound-reached" src="data:audio/mpeg;base64,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" i18n-processed=""></audio>
4257 </template>
4258 </div>
4259
4260
4261<script jstcache="0">// Copyright (c) 2012 The Chromium Authors. All rights reserved.
4262// Use of this source code is governed by a BSD-style license that can be
4263// found in the LICENSE file.
4264
4265/**
4266 * @fileoverview This file defines a singleton which provides access to all data
4267 * that is available as soon as the page's resources are loaded (before DOM
4268 * content has finished loading). This data includes both localized strings and
4269 * any data that is important to have ready from a very early stage (e.g. things
4270 * that must be displayed right away).
4271 *
4272 * Note that loadTimeData is not guaranteed to be consistent between page
4273 * refreshes (https://crbug.com/740629) and should not contain values that might
4274 * change if the page is re-opened later.
4275 */
4276
4277/**
4278 * @typedef {{
4279 * substitutions: (Array<string>|undefined),
4280 * attrs: (Object<function(Node, string):boolean>|undefined),
4281 * tags: (Array<string>|undefined),
4282 * }}
4283 */
4284var SanitizeInnerHtmlOpts;
4285
4286/** @type {!LoadTimeData} */ var loadTimeData;
4287
4288// Expose this type globally as a temporary work around until
4289// https://github.com/google/closure-compiler/issues/544 is fixed.
4290/** @constructor */
4291function LoadTimeData(){}
4292
4293(function() {
4294 'use strict';
4295
4296 LoadTimeData.prototype = {
4297 /**
4298 * Sets the backing object.
4299 *
4300 * Note that there is no getter for |data_| to discourage abuse of the form:
4301 *
4302 * var value = loadTimeData.data()['key'];
4303 *
4304 * @param {Object} value The de-serialized page data.
4305 */
4306 set data(value) {
4307 expect(!this.data_, 'Re-setting data.');
4308 this.data_ = value;
4309 },
4310
4311 /**
4312 * Returns a JsEvalContext for |data_|.
4313 * @returns {JsEvalContext}
4314 */
4315 createJsEvalContext: function() {
4316 return new JsEvalContext(this.data_);
4317 },
4318
4319 /**
4320 * @param {string} id An ID of a value that might exist.
4321 * @return {boolean} True if |id| is a key in the dictionary.
4322 */
4323 valueExists: function(id) {
4324 return id in this.data_;
4325 },
4326
4327 /**
4328 * Fetches a value, expecting that it exists.
4329 * @param {string} id The key that identifies the desired value.
4330 * @return {*} The corresponding value.
4331 */
4332 getValue: function(id) {
4333 expect(this.data_, 'No data. Did you remember to include strings.js?');
4334 var value = this.data_[id];
4335 expect(typeof value != 'undefined', 'Could not find value for ' + id);
4336 return value;
4337 },
4338
4339 /**
4340 * As above, but also makes sure that the value is a string.
4341 * @param {string} id The key that identifies the desired string.
4342 * @return {string} The corresponding string value.
4343 */
4344 getString: function(id) {
4345 var value = this.getValue(id);
4346 expectIsType(id, value, 'string');
4347 return /** @type {string} */ (value);
4348 },
4349
4350 /**
4351 * Returns a formatted localized string where $1 to $9 are replaced by the
4352 * second to the tenth argument.
4353 * @param {string} id The ID of the string we want.
4354 * @param {...(string|number)} var_args The extra values to include in the
4355 * formatted output.
4356 * @return {string} The formatted string.
4357 */
4358 getStringF: function(id, var_args) {
4359 var value = this.getString(id);
4360 if (!value)
4361 return '';
4362
4363 var args = Array.prototype.slice.call(arguments);
4364 args[0] = value;
4365 return this.substituteString.apply(this, args);
4366 },
4367
4368 /**
4369 * Make a string safe for use with with Polymer bindings that are
4370 * inner-h-t-m-l (or other innerHTML use).
4371 * @param {string} rawString The unsanitized string.
4372 * @param {SanitizeInnerHtmlOpts=} opts Optional additional allowed tags and
4373 * attributes.
4374 * @return {string}
4375 */
4376 sanitizeInnerHtml: function(rawString, opts) {
4377 opts = opts || {};
4378 return parseHtmlSubset('<b>' + rawString + '</b>', opts.tags, opts.attrs)
4379 .firstChild.innerHTML;
4380 },
4381
4382 /**
4383 * Returns a formatted localized string where $1 to $9 are replaced by the
4384 * second to the tenth argument. Any standalone $ signs must be escaped as
4385 * $$.
4386 * @param {string} label The label to substitute through.
4387 * This is not an resource ID.
4388 * @param {...(string|number)} var_args The extra values to include in the
4389 * formatted output.
4390 * @return {string} The formatted string.
4391 */
4392 substituteString: function(label, var_args) {
4393 var varArgs = arguments;
4394 return label.replace(/\$(.|$|\n)/g, function(m) {
4395 assert(m.match(/\$[$1-9]/), 'Unescaped $ found in localized string.');
4396 return m == '$$' ? '$' : varArgs[m[1]];
4397 });
4398 },
4399
4400 /**
4401 * Returns a formatted string where $1 to $9 are replaced by the second to
4402 * tenth argument, split apart into a list of pieces describing how the
4403 * substitution was performed. Any standalone $ signs must be escaped as $$.
4404 * @param {string} label A localized string to substitute through.
4405 * This is not an resource ID.
4406 * @param {...(string|number)} var_args The extra values to include in the
4407 * formatted output.
4408 * @return {!Array<!{value: string, arg: (null|string)}>} The formatted
4409 * string pieces.
4410 */
4411 getSubstitutedStringPieces: function(label, var_args) {
4412 var varArgs = arguments;
4413 // Split the string by separately matching all occurrences of $1-9 and of
4414 // non $1-9 pieces.
4415 var pieces = (label.match(/(\$[1-9])|(([^$]|\$([^1-9]|$))+)/g) ||
4416 []).map(function(p) {
4417 // Pieces that are not $1-9 should be returned after replacing $$
4418 // with $.
4419 if (!p.match(/^\$[1-9]$/)) {
4420 assert(
4421 (p.match(/\$/g) || []).length % 2 == 0,
4422 'Unescaped $ found in localized string.');
4423 return {value: p.replace(/\$\$/g, '$'), arg: null};
4424 }
4425
4426 // Otherwise, return the substitution value.
4427 return {value: varArgs[p[1]], arg: p};
4428 });
4429
4430 return pieces;
4431 },
4432
4433 /**
4434 * As above, but also makes sure that the value is a boolean.
4435 * @param {string} id The key that identifies the desired boolean.
4436 * @return {boolean} The corresponding boolean value.
4437 */
4438 getBoolean: function(id) {
4439 var value = this.getValue(id);
4440 expectIsType(id, value, 'boolean');
4441 return /** @type {boolean} */ (value);
4442 },
4443
4444 /**
4445 * As above, but also makes sure that the value is an integer.
4446 * @param {string} id The key that identifies the desired number.
4447 * @return {number} The corresponding number value.
4448 */
4449 getInteger: function(id) {
4450 var value = this.getValue(id);
4451 expectIsType(id, value, 'number');
4452 expect(value == Math.floor(value), 'Number isn\'t integer: ' + value);
4453 return /** @type {number} */ (value);
4454 },
4455
4456 /**
4457 * Override values in loadTimeData with the values found in |replacements|.
4458 * @param {Object} replacements The dictionary object of keys to replace.
4459 */
4460 overrideValues: function(replacements) {
4461 expect(
4462 typeof replacements == 'object',
4463 'Replacements must be a dictionary object.');
4464 for (var key in replacements) {
4465 this.data_[key] = replacements[key];
4466 }
4467 }
4468 };
4469
4470 /**
4471 * Checks condition, displays error message if expectation fails.
4472 * @param {*} condition The condition to check for truthiness.
4473 * @param {string} message The message to display if the check fails.
4474 */
4475 function expect(condition, message) {
4476 if (!condition) {
4477 console.error(
4478 'Unexpected condition on ' + document.location.href + ': ' + message);
4479 }
4480 }
4481
4482 /**
4483 * Checks that the given value has the given type.
4484 * @param {string} id The id of the value (only used for error message).
4485 * @param {*} value The value to check the type on.
4486 * @param {string} type The type we expect |value| to be.
4487 */
4488 function expectIsType(id, value, type) {
4489 expect(
4490 typeof value == type, '[' + value + '] (' + id + ') is not a ' + type);
4491 }
4492
4493 expect(!loadTimeData, 'should only include this file once');
4494 loadTimeData = new LoadTimeData;
4495})();
4496</script><script jstcache="0">loadTimeData.data = {"details":"Detalhes","errorCode":"ERR_NAME_NOT_RESOLVED","fontfamily":"'Segoe UI', Tahoma, sans-serif","fontsize":"75%","heading":{"hostName":"www.com.br","msg":"Não é possÃvel acessar esse site"},"hideDetails":"Ocultar detalhes","iconClass":"icon-generic","language":"pt","suggestionsSummaryList":[{"searchTerms":"br","searchUrl":"https://www.google.com.br/search?q=br","summary":"Pesquise \u003Ca jsvalues=\"href:searchUrl;.jstdata:$this\" onclick=\"linkClicked(this.jstdata)\" jscontent=\"searchTerms\" id=\"search-link\">\u003C/a> no Google","trackingId":0}],"summary":{"failedUrl":"http://www.com.br/","hostName":"www.com.br","msg":"Não foi possÃvel encontrar o endereço IP do servidor de \u003Cstrong jscontent=\"hostName\">\u003C/strong>."},"textdirection":"ltr","title":"www.com.br"};</script><script jstcache="0">// Copyright (c) 2012 The Chromium Authors. All rights reserved.
4497// Use of this source code is governed by a BSD-style license that can be
4498// found in the LICENSE file.
4499
4500// Note: vulcanize sometimes disables GRIT processing. If you're importing i18n
4501// stuff with <link rel="import">, you should probably be using
4502// html/i18n_template.html instead of this file.
4503
4504// // Copyright (c) 2012 The Chromium Authors. All rights reserved.
4505// Use of this source code is governed by a BSD-style license that can be
4506// found in the LICENSE file.
4507
4508/** @typedef {Document|DocumentFragment|Element} */
4509var ProcessingRoot;
4510
4511/**
4512 * @fileoverview This is a simple template engine inspired by JsTemplates
4513 * optimized for i18n.
4514 *
4515 * It currently supports three handlers:
4516 *
4517 * * i18n-content which sets the textContent of the element.
4518 *
4519 * <span i18n-content="myContent"></span>
4520 *
4521 * * i18n-options which generates <option> elements for a <select>.
4522 *
4523 * <select i18n-options="myOptionList"></select>
4524 *
4525 * * i18n-values is a list of attribute-value or property-value pairs.
4526 * Properties are prefixed with a '.' and can contain nested properties.
4527 *
4528 * <span i18n-values="title:myTitle;.style.fontSize:fontSize"></span>
4529 *
4530 * This file is a copy of i18n_template.js, with minor tweaks to support using
4531 * load_time_data.js. It should replace i18n_template.js eventually.
4532 */
4533
4534var i18nTemplate = (function() {
4535 /**
4536 * This provides the handlers for the templating engine. The key is used as
4537 * the attribute name and the value is the function that gets called for every
4538 * single node that has this attribute.
4539 * @type {!Object}
4540 */
4541 var handlers = {
4542 /**
4543 * This handler sets the textContent of the element.
4544 * @param {!HTMLElement} element The node to modify.
4545 * @param {string} key The name of the value in |data|.
4546 * @param {!LoadTimeData} data The data source to draw from.
4547 * @param {!Set<ProcessingRoot>} visited
4548 */
4549 'i18n-content': function(element, key, data, visited) {
4550 element.textContent = data.getString(key);
4551 },
4552
4553 /**
4554 * This handler adds options to a <select> element.
4555 * @param {!HTMLElement} select The node to modify.
4556 * @param {string} key The name of the value in |data|. It should
4557 * identify an array of values to initialize an <option>. Each value,
4558 * if a pair, represents [content, value]. Otherwise, it should be a
4559 * content string with no value.
4560 * @param {!LoadTimeData} data The data source to draw from.
4561 * @param {!Set<ProcessingRoot>} visited
4562 */
4563 'i18n-options': function(select, key, data, visited) {
4564 var options = data.getValue(key);
4565 options.forEach(function(optionData) {
4566 var option = typeof optionData == 'string' ?
4567 new Option(optionData) :
4568 new Option(optionData[1], optionData[0]);
4569 select.appendChild(option);
4570 });
4571 },
4572
4573 /**
4574 * This is used to set HTML attributes and DOM properties. The syntax is:
4575 * attributename:key;
4576 * .domProperty:key;
4577 * .nested.dom.property:key
4578 * @param {!HTMLElement} element The node to modify.
4579 * @param {string} attributeAndKeys The path of the attribute to modify
4580 * followed by a colon, and the name of the value in |data|.
4581 * Multiple attribute/key pairs may be separated by semicolons.
4582 * @param {!LoadTimeData} data The data source to draw from.
4583 * @param {!Set<ProcessingRoot>} visited
4584 */
4585 'i18n-values': function(element, attributeAndKeys, data, visited) {
4586 var parts = attributeAndKeys.replace(/\s/g, '').split(/;/);
4587 parts.forEach(function(part) {
4588 if (!part)
4589 return;
4590
4591 var attributeAndKeyPair = part.match(/^([^:]+):(.+)$/);
4592 if (!attributeAndKeyPair)
4593 throw new Error('malformed i18n-values: ' + attributeAndKeys);
4594
4595 var propName = attributeAndKeyPair[1];
4596 var propExpr = attributeAndKeyPair[2];
4597
4598 var value = data.getValue(propExpr);
4599
4600 // Allow a property of the form '.foo.bar' to assign a value into
4601 // element.foo.bar.
4602 if (propName[0] == '.') {
4603 var path = propName.slice(1).split('.');
4604 var targetObject = element;
4605 while (targetObject && path.length > 1) {
4606 targetObject = targetObject[path.shift()];
4607 }
4608 if (targetObject) {
4609 targetObject[path] = value;
4610 // In case we set innerHTML (ignoring others) we need to recursively
4611 // check the content.
4612 if (path == 'innerHTML') {
4613 for (var i = 0; i < element.children.length; ++i) {
4614 processWithoutCycles(element.children[i], data, visited, false);
4615 }
4616 }
4617 }
4618 } else {
4619 element.setAttribute(propName, /** @type {string} */ (value));
4620 }
4621 });
4622 }
4623 };
4624
4625 var prefixes = [''];
4626
4627 // Only look through shadow DOM when it's supported. As of April 2015, iOS
4628 // Chrome doesn't support shadow DOM.
4629 if (Element.prototype.createShadowRoot)
4630 prefixes.push('* /deep/ ');
4631
4632 var attributeNames = Object.keys(handlers);
4633 var selector = prefixes
4634 .map(function(prefix) {
4635 return prefix + '[' +
4636 attributeNames.join('], ' + prefix + '[') + ']';
4637 })
4638 .join(', ');
4639
4640 /**
4641 * Processes a DOM tree using a |data| source to populate template values.
4642 * @param {!ProcessingRoot} root The root of the DOM tree to process.
4643 * @param {!LoadTimeData} data The data to draw from.
4644 */
4645 function process(root, data) {
4646 processWithoutCycles(root, data, new Set(), true);
4647 }
4648
4649 /**
4650 * Internal process() method that stops cycles while processing.
4651 * @param {!ProcessingRoot} root
4652 * @param {!LoadTimeData} data
4653 * @param {!Set<ProcessingRoot>} visited Already visited roots.
4654 * @param {boolean} mark Whether nodes should be marked processed.
4655 */
4656 function processWithoutCycles(root, data, visited, mark) {
4657 if (visited.has(root)) {
4658 // Found a cycle. Stop it.
4659 return;
4660 }
4661
4662 // Mark the node as visited before recursing.
4663 visited.add(root);
4664
4665 var importLinks = root.querySelectorAll('link[rel=import]');
4666 for (var i = 0; i < importLinks.length; ++i) {
4667 var importLink = /** @type {!HTMLLinkElement} */ (importLinks[i]);
4668 if (!importLink.import) {
4669 // Happens when a <link rel=import> is inside a <template>.
4670 // TODO(dbeam): should we log an error if we detect that here?
4671 continue;
4672 }
4673 processWithoutCycles(importLink.import, data, visited, mark);
4674 }
4675
4676 var templates = root.querySelectorAll('template');
4677 for (var i = 0; i < templates.length; ++i) {
4678 var template = /** @type {HTMLTemplateElement} */ (templates[i]);
4679 if (!template.content)
4680 continue;
4681 processWithoutCycles(template.content, data, visited, mark);
4682 }
4683
4684 var isElement = root instanceof Element;
4685 if (isElement && root.webkitMatchesSelector(selector))
4686 processElement(/** @type {!Element} */ (root), data, visited);
4687
4688 var elements = root.querySelectorAll(selector);
4689 for (var i = 0; i < elements.length; ++i) {
4690 processElement(elements[i], data, visited);
4691 }
4692
4693 if (mark) {
4694 var processed = isElement ? [root] : root.children;
4695 if (processed) {
4696 for (var i = 0; i < processed.length; ++i) {
4697 processed[i].setAttribute('i18n-processed', '');
4698 }
4699 }
4700 }
4701 }
4702
4703 /**
4704 * Run through various [i18n-*] attributes and populate.
4705 * @param {!Element} element
4706 * @param {!LoadTimeData} data
4707 * @param {!Set<ProcessingRoot>} visited
4708 */
4709 function processElement(element, data, visited) {
4710 for (var i = 0; i < attributeNames.length; i++) {
4711 var name = attributeNames[i];
4712 var attribute = element.getAttribute(name);
4713 if (attribute != null)
4714 handlers[name](element, attribute, data, visited);
4715 }
4716 }
4717
4718 return {process: process};
4719}());
4720
4721// // Copyright 2017 The Chromium Authors. All rights reserved.
4722// Use of this source code is governed by a BSD-style license that can be
4723// found in the LICENSE file.
4724
4725i18nTemplate.process(document, loadTimeData);
4726
4727</script><script jstcache="0">// Copyright (c) 2012 The Chromium Authors. All rights reserved.
4728// Use of this source code is governed by a BSD-style license that can be
4729// found in the LICENSE file.
4730
4731// This file serves as a proxy to bring the included js file from /third_party
4732// into its correct location under the resources directory tree, whence it is
4733// delivered via a chrome://resources URL. See ../webui_resources.grd.
4734
4735// Note: this <include> is not behind a single-line comment because the first
4736// line of the file is source code (so the first line would be skipped) instead
4737// of a licence header.
4738// clang-format off
4739(function(){var i=null;function k(){return Function.prototype.call.apply(Array.prototype.slice,arguments)}function l(a,b){var c=k(arguments,2);return function(){return b.apply(a,c)}}function m(a,b){var c=new n(b);for(c.f=[a];c.f.length;){var e=c,d=c.f.shift();e.g(d);for(d=d.firstChild;d;d=d.nextSibling)d.nodeType==1&&e.f.push(d)}}function n(a){this.g=a}function o(a){a.style.display=""}function p(a){a.style.display="none"};var q=":",r=/\s*;\s*/;function s(){this.i.apply(this,arguments)}s.prototype.i=function(a,b){if(!this.a)this.a={};if(b){var c=this.a,e=b.a,d;for(d in e)c[d]=e[d]}else for(c in d=this.a,e=t,e)d[c]=e[c];this.a.$this=a;this.a.$context=this;this.d=typeof a!="undefined"&&a!=i?a:"";if(!b)this.a.$top=this.d};var t={$default:i},u=[];function v(a){for(var b in a.a)delete a.a[b];a.d=i;u.push(a)}function w(a,b,c){try{return b.call(c,a.a,a.d)}catch(e){return t.$default}}
4740function x(a,b,c,e){if(u.length>0){var d=u.pop();s.call(d,b,a);a=d}else a=new s(b,a);a.a.$index=c;a.a.$count=e;return a}var y="a_",z="b_",A="with (a_) with (b_) return ",D={};function E(a){if(!D[a])try{D[a]=new Function(y,z,A+a)}catch(b){}return D[a]}function F(a){for(var b=[],a=a.split(r),c=0,e=a.length;c<e;++c){var d=a[c].indexOf(q);if(!(d<0)){var f;f=a[c].substr(0,d).replace(/^\s+/,"").replace(/\s+$/,"");d=E(a[c].substr(d+1));b.push(f,d)}}return b};var G="jsinstance",H="jsts",I="*",J="div",K="id";function L(){}var M=0,N={0:{}},P={},Q={},R=[];function S(a){a.__jstcache||m(a,function(a){T(a)})}var U=[["jsselect",E],["jsdisplay",E],["jsvalues",F],["jsvars",F],["jseval",function(a){for(var b=[],a=a.split(r),c=0,e=a.length;c<e;++c)if(a[c]){var d=E(a[c]);b.push(d)}return b}],["transclude",function(a){return a}],["jscontent",E],["jsskip",E]];
4741function T(a){if(a.__jstcache)return a.__jstcache;var b=a.getAttribute("jstcache");if(b!=i)return a.__jstcache=N[b];for(var b=R.length=0,c=U.length;b<c;++b){var e=U[b][0],d=a.getAttribute(e);Q[e]=d;d!=i&&R.push(e+"="+d)}if(R.length==0)return a.setAttribute("jstcache","0"),a.__jstcache=N[0];var f=R.join("&");if(b=P[f])return a.setAttribute("jstcache",b),a.__jstcache=N[b];for(var h={},b=0,c=U.length;b<c;++b){var d=U[b],e=d[0],g=d[1],d=Q[e];d!=i&&(h[e]=g(d))}b=""+ ++M;a.setAttribute("jstcache",b);N[b]=
4742h;P[f]=b;return a.__jstcache=h}function V(a,b){a.h.push(b);a.k.push(0)}function W(a){return a.c.length?a.c.pop():[]}
4743L.prototype.e=function(a,b){var c=X(b),e=c.transclude;if(e)(c=Y(e))?(b.parentNode.replaceChild(c,b),e=W(this),e.push(this.e,a,c),V(this,e)):b.parentNode.removeChild(b);else if(c=c.jsselect){var c=w(a,c,b),d=b.getAttribute(G),f=!1;d&&(d.charAt(0)==I?(d=parseInt(d.substr(1),10),f=!0):d=parseInt(d,10));var h=c!=i&&typeof c=="object"&&typeof c.length=="number",e=h?c.length:1,g=h&&e==0;if(h)if(g)d?b.parentNode.removeChild(b):(b.setAttribute(G,"*0"),p(b));else if(o(b),d===i||d===""||f&&d<e-1){f=W(this);
4744d=d||0;for(h=e-1;d<h;++d){var j=b.cloneNode(!0);b.parentNode.insertBefore(j,b);Z(j,c,d);g=x(a,c[d],d,e);f.push(this.b,g,j,v,g,i)}Z(b,c,d);g=x(a,c[d],d,e);f.push(this.b,g,b,v,g,i);V(this,f)}else d<e?(f=c[d],Z(b,c,d),g=x(a,f,d,e),f=W(this),f.push(this.b,g,b,v,g,i),V(this,f)):b.parentNode.removeChild(b);else c==i?p(b):(o(b),g=x(a,c,0,1),f=W(this),f.push(this.b,g,b,v,g,i),V(this,f))}else this.b(a,b)};
4745L.prototype.b=function(a,b){var c=X(b),e=c.jsdisplay;if(e){if(!w(a,e,b)){p(b);return}o(b)}if(e=c.jsvars)for(var d=0,f=e.length;d<f;d+=2){var h=e[d],g=w(a,e[d+1],b);a.a[h]=g}if(e=c.jsvalues){d=0;for(f=e.length;d<f;d+=2)if(g=e[d],h=w(a,e[d+1],b),g.charAt(0)=="$")a.a[g]=h;else if(g.charAt(0)=="."){for(var g=g.substr(1).split("."),j=b,O=g.length,B=0,$=O-1;B<$;++B){var C=g[B];j[C]||(j[C]={});j=j[C]}j[g[O-1]]=h}else g&&(typeof h=="boolean"?h?b.setAttribute(g,g):b.removeAttribute(g):b.setAttribute(g,""+
4746h))}if(e=c.jseval){d=0;for(f=e.length;d<f;++d)w(a,e[d],b)}e=c.jsskip;if(!e||!w(a,e,b))if(c=c.jscontent){if(c=""+w(a,c,b),b.innerHTML!=c){for(;b.firstChild;)e=b.firstChild,e.parentNode.removeChild(e);b.appendChild(this.j.createTextNode(c))}}else{c=W(this);for(e=b.firstChild;e;e=e.nextSibling)e.nodeType==1&&c.push(this.e,a,e);c.length&&V(this,c)}};function X(a){if(a.__jstcache)return a.__jstcache;var b=a.getAttribute("jstcache");if(b)return a.__jstcache=N[b];return T(a)}
4747function Y(a,b){var c=document;if(b){var e=c.getElementById(a);if(!e){var e=b(),d=H,f=c.getElementById(d);if(!f)f=c.createElement(J),f.id=d,p(f),f.style.position="absolute",c.body.appendChild(f);d=c.createElement(J);f.appendChild(d);d.innerHTML=e;e=c.getElementById(a)}c=e}else c=c.getElementById(a);return c?(S(c),c=c.cloneNode(!0),c.removeAttribute(K),c):i}function Z(a,b,c){c==b.length-1?a.setAttribute(G,I+c):a.setAttribute(G,""+c)};window.jstGetTemplate=Y;window.JsEvalContext=s;window.jstProcess=function(a,b){var c=new L;S(b);c.j=b?b.nodeType==9?b:b.ownerDocument||document:document;var e=l(c,c.e,a,b),d=c.h=[],f=c.k=[];c.c=[];e();for(var h,g,j;d.length;)h=d[d.length-1],e=f[f.length-1],e>=h.length?(e=c,g=d.pop(),g.length=0,e.c.push(g),f.pop()):(g=h[e++],j=h[e++],h=h[e++],f[f.length-1]=e,g.call(c,j,h))};
4748})()
4749</script><script jstcache="0">var tp = document.getElementById('t');jstProcess(loadTimeData.createJsEvalContext(), tp);</script></body></html>