- add sources.
[platform/framework/web/crosswalk.git] / src / ui / keyboard / resources / elements / kb-keyboard.html
1 <!--
2   -- Copyright 2013 The Chromium Authors. All rights reserved.
3   -- Use of this source code is governed by a BSD-style license that can be
4   -- found in the LICENSE file.
5   -->
6
7 <polymer-element name="kb-keyboard" on-key-over="keyOver" on-key-up="keyUp"
8     on-key-down="keyDown" on-key-longpress="keyLongpress" on-pointerup="up"
9     on-pointerdown="down" on-enable-sel="enableSel"
10     on-enable-dbl="enableDbl"  on-key-out="keyOut" on-show-options="showOptions"
11     on-set-layout="setLayout"
12     attributes="keyset layout inputType inputTypeToLayoutMap">
13   <template>
14     <style>
15       @host {
16         * {
17           position: relative;
18         }
19       }
20     </style>
21     <!-- The ID for a keyset follows the naming convention of combining the
22       -- layout name with a base keyset name. This convention is used to
23       -- allow multiple layouts to be loaded (enablign fast switching) while
24       -- allowing the shift and spacebar keys to be common across multiple
25       -- keyboard layouts.
26       -->
27     <content select="#{{layout}}-{{keyset}}"></content>
28     <kb-keyboard-overlay id="overlay" hidden></kb-keyboard-overlay>
29     <kb-key-codes id="keyCodeMetadata"></kb-key-codes>
30   </template>
31   <script>
32     /**
33      * The repeat delay in milliseconds before a key starts repeating. Use the
34      * same rate as Chromebook.
35      * (See chrome/browser/chromeos/language_preferences.cc)
36      * @const
37      * @type {number}
38      */
39     var REPEAT_DELAY_MSEC = 500;
40
41     /**
42      * The repeat interval or number of milliseconds between subsequent
43      * keypresses. Use the same rate as Chromebook.
44      * @const
45      * @type {number}
46      */
47     var REPEAT_INTERVAL_MSEC = 50;
48
49     /**
50      * The double click/tap interval.
51      * @const
52      * @type {number}
53      */
54     var DBL_INTERVAL_MSEC = 300;
55
56     /**
57      * The index of the name of the keyset when searching for all keysets.
58      * @const
59      * @type {number}
60      */
61     var REGEX_KEYSET_INDEX = 1;
62
63     /**
64      * The integer number of matches when searching for keysets.
65      * @const
66      * @type {number}
67      */
68     var REGEX_MATCH_COUNT = 2;
69
70     /**
71      * The boolean to decide if keyboard should transit to upper case keyset
72      * when spacebar is pressed. If a closing punctuation is followed by a
73      * spacebar, keyboard should automatically transit to upper case.
74      * @type {boolean}
75      */
76     var enterUpperOnSpace = false;
77
78     /**
79      * A structure to track the currently repeating key on the keyboard.
80      */
81     var repeatKey = {
82
83       /**
84         * The timer for the delay before repeating behaviour begins.
85         * @type {number|undefined}
86         */
87       timer: undefined,
88
89       /**
90        * The interval timer for issuing keypresses of a repeating key.
91        * @type {number|undefined}
92        */
93       interval: undefined,
94
95       /**
96        * The key which is currently repeating.
97        * @type {BaseKey|undefined}
98        */
99       key: undefined,
100
101       /**
102        * Cancel the repeat timers of the currently active key.
103        */
104       cancel: function() {
105         clearTimeout(this.timer);
106         clearInterval(this.interval);
107         this.timer = undefined;
108         this.interval = undefined;
109         this.key = undefined;
110       }
111     };
112
113     /**
114      * The minimum movement interval needed to trigger cursor move on
115      * horizontal and vertical way.
116      * @const
117      * @type {number}
118      */
119     var MIN_SWIPE_DIST = 60;
120
121     /**
122      * The boolean to decide if it is swipe in process or finished.
123      * @type {boolean}
124      */
125     var swipeInProgress = false;
126
127     /**
128      * A boolean used to track if the keyboard is ready for user input. As
129      * alternate layouts are dynamically loaded, the keyboard may be in a state
130      * where it is not fully initialized until all links, key-sequences, and
131      * imports are fully resolved.
132      */
133     var isReady = false;
134
135     // Flag values for ctrl, alt and shift as defined by EventFlags
136     // in "event_constants.h".
137     // @enum {number}
138     var Modifier = {
139       NONE: 0,
140       ALT: 8,
141       CONTROL: 4,
142       SHIFT: 2
143     };
144
145     /**
146      * The enumeration of swipe directions.
147      * @const
148      * @type {Enum}
149      */
150     var SWIPE_DIRECTION = {
151       RIGHT: 0x1,
152       LEFT: 0x2,
153       UP: 0x4,
154       DOWN: 0x8
155     };
156
157     /**
158      * A structure to track the current swipe status.
159      */
160     var swipeStatus = {
161
162       /**
163        * The count of horizontal and vertical movement.
164        * @type {number}
165        */
166        offset_x : 0,
167        offset_y : 0,
168
169       /**
170        * Last touch coordinate.
171        * @type {number}
172        */
173       pre_x : 0,
174       pre_y : 0,
175
176       /**
177        * The flag of current modifier key.
178        * @type {number}
179        */
180       swipeFlags : 0,
181
182       /**
183        * Current swipe direction.
184        * @type {number}
185        */
186       swipeDirection : 0,
187
188       /**
189        * The number of times we've swiped within a single swipe.
190        * @type {number}
191        */
192       swipeIndex: 0,
193
194       /**
195        * Reset all the values when swipe finished.
196        */
197       resetAll: function() {
198         this.offset_x = 0;
199         this.offset_y = 0;
200         this.pre_x = 0;
201         this.pre_y = 0;
202         this.swipeFlags = 0;
203         this.swipeDirection = 0;
204         this.swipeIndex = 0;
205       }
206     };
207
208     Polymer('kb-keyboard', {
209       alt: null,
210       control: null,
211       dblDetail_: null,
212       dblTimer_: null,
213       inputType: null,
214       lastPressedKey: null,
215       shift: null,
216       swipeHandler: null,
217       voiceInput_: null,
218
219       /**
220        * The default input type to keyboard layout map. The key must be one of
221        * the input box type values.
222        * @type {object}
223        */
224       inputTypeToLayoutMap: {
225         number: "numeric",
226         text: "qwerty",
227         password: "qwerty"
228       },
229
230       /**
231        * Changes the current keyset.
232        * @param {Object} detail The detail of the event that called this
233        *     function.
234        */
235       changeKeyset: function(detail) {
236         if (detail.relegateToShift && this.shift) {
237           this.keyset = this.shift.textKeyset;
238           this.activeKeyset.nextKeyset = undefined;
239           return true;
240         }
241         var toKeyset = detail.toKeyset;
242         if (toKeyset) {
243           this.keyset = toKeyset;
244           this.activeKeyset.nextKeyset = detail.nextKeyset;
245           return true;
246         }
247         return false;
248       },
249
250       ready: function() {
251         this.voiceInput_ = new VoiceInput(this);
252         this.swipeHandler = this.onSwipeUpdate.bind(this);
253       },
254
255       /**
256        * Called when the type of focused input box changes. If a keyboard layout
257        * is defined for the current input type, that layout will be loaded.
258        * Otherwise, the keyboard layout for 'text' type will be loaded.
259        */
260       inputTypeChanged: function() {
261         // TODO(bshe): Toggle visibility of some keys in a keyboard layout
262         // according to the input type.
263         var layout = this.inputTypeToLayoutMap[this.inputType];
264         if (!layout)
265           layout = this.inputTypeToLayoutMap.text;
266         this.layout = layout;
267       },
268
269       /**
270        * When double click/tap event is enabled, the second key-down and key-up
271        * events on the same key should be skipped. Return true when the event
272        * with |detail| should be skipped.
273        * @param {Object} detail The detail of key-up or key-down event.
274        */
275       skipEvent: function(detail) {
276         if (this.dblDetail_) {
277           if (this.dblDetail_.char != detail.char) {
278             // The second key down is not on the same key. Double click/tap
279             // should be ignored.
280             this.dblDetail_ = null;
281             clearTimeout(this.dblTimer_);
282           } else if (this.dblDetail_.clickCount == 1) {
283             return true;
284           }
285         }
286         return false;
287       },
288
289       /**
290        * This function is bound to swipeHandler. And swipeHandler handle
291        * the pointermove event after pointerdown event happened.
292        * @para {PointerEvent} event.
293        */
294       onSwipeUpdate: function(event) {
295         if (!event.isPrimary)
296           return;
297         swipeStatus.offset_x += event.screenX - swipeStatus.pre_x;
298         // swipeStatus.offset_y += event.screenY - swipeStatus.pre_y;
299         if (Math.abs(swipeStatus.offset_x) > MIN_SWIPE_DIST ||
300             Math.abs(swipeStatus.offset_y) > MIN_SWIPE_DIST) {
301           swipeInProgress = true;
302           if (this.lastPressedKey) {
303             this.lastPressedKey.classList.remove('active');
304             this.lastPressedKey = null;
305           }
306         }
307         if (swipeStatus.offset_x > MIN_SWIPE_DIST) {
308           swipeStatus.swipeDirection |= SWIPE_DIRECTION.RIGHT;
309           swipeStatus.swipeIndex++;
310           swipeStatus.offset_x = 0;
311         } else if (swipeStatus.offset_x < -MIN_SWIPE_DIST) {
312           swipeStatus.swipeDirection |= SWIPE_DIRECTION.LEFT;
313           swipeStatus.swipeIndex--;
314           swipeStatus.offset_x = 0;
315         }
316         // Swipe vertically only when the swipe reaches the gradient of 45
317         // degree. This can also be larger.
318         if (Math.abs(event.screenY - swipeStatus.pre_y) >
319             Math.abs(event.screenX - swipeStatus.pre_x)) {
320           if (swipeStatus.offset_y > MIN_SWIPE_DIST) {
321             swipeStatus.swipeDirection |= SWIPE_DIRECTION.DOWN;
322             swipeStatus.offset_y = 0;
323           } else if (swipeStatus.offset_y < -MIN_SWIPE_DIST) {
324             swipeStatus.swipeDirection |= SWIPE_DIRECTION.UP;
325             swipeStatus.offset_y = 0;
326           }
327         }
328         if (swipeStatus.swipeDirection) {
329           var modifiers = 0;
330           if (swipeStatus.swipeIndex % 2 != 0) {
331             modifiers |= Modifier.SHIFT;
332             modifiers |= Modifier.CONTROL;
333           }
334           MoveCursor(swipeStatus.swipeDirection, modifiers);
335           swipeStatus.swipeDirection = 0;
336         }
337         swipeStatus.pre_x = event.screenX;
338         swipeStatus.pre_y = event.screenY;
339       },
340
341       /**
342        * Handles key-down event that is sent by kb-key-base.
343        * @param {CustomEvent} event The key-down event dispatched by
344        *     kb-key-base.
345        * @param {Object} detail The detail of pressed kb-key.
346        */
347       keyDown: function(event, detail) {
348         if (this.skipEvent(detail))
349           return;
350
351         if (this.lastPressedKey) {
352           this.lastPressedKey.classList.remove('active');
353           this.lastPressedKey.autoRelease();
354         }
355         this.lastPressedKey = event.target;
356         this.lastPressedKey.classList.add('active');
357         repeatKey.cancel();
358
359         var char = detail.char;
360         switch(char) {
361           case 'Shift':
362             this.classList.remove('caps-locked');
363             break;
364           case 'Alt':
365           case 'Ctrl':
366             var modifier = char.toLowerCase() + "-active";
367             // Removes modifier if already active.
368             if (this.classList.contains(modifier))
369               this.classList.remove(modifier);
370             break;
371           default:
372             // Notify shift key.
373             if (this.shift)
374               this.shift.onNonControlKeyDown();
375             if (this.ctrl)
376               this.ctrl.onNonControlKeyDown();
377             if (this.alt)
378               this.alt.onNonControlKeyDown();
379             break;
380         }
381         if(this.changeKeyset(detail))
382           return;
383         if (detail.repeat) {
384           this.keyTyped(detail);
385           this.onNonControlKeyTyped();
386           repeatKey.key = this.lastPressedKey;
387           var self = this;
388           repeatKey.timer = setTimeout(function() {
389             repeatKey.timer = undefined;
390             repeatKey.interval = setInterval(function() {
391                self.keyTyped(detail);
392             }, REPEAT_INTERVAL_MSEC);
393           }, Math.max(0, REPEAT_DELAY_MSEC - REPEAT_INTERVAL_MSEC));
394         }
395       },
396
397       /**
398        * Handles key-out event that is sent by kb-shift-key.
399        * @param {CustomEvent} event The key-out event dispatched by
400        *     kb-shift-key.
401        * @param {Object} detail The detail of pressed kb-shift-key.
402        */
403       keyOut: function(event, detail) {
404         this.changeKeyset(detail);
405       },
406
407       /**
408        * Enable/start double click/tap event recognition.
409        * @param {CustomEvent} event The enable-dbl event dispatched by
410        *     kb-shift-key.
411        * @param {Object} detail The detail of pressed kb-shift-key.
412        */
413       enableDbl: function(event, detail) {
414         if (!this.dblDetail_) {
415           this.dblDetail_ = detail;
416           this.dblDetail_.clickCount = 0;
417           var self = this;
418           this.dblTimer_ = setTimeout(function() {
419             self.dblDetail_.callback = null;
420             self.dblDetail_ = null;
421           }, DBL_INTERVAL_MSEC);
422         }
423       },
424
425       /**
426        * Enable the selection while swipe.
427        * @param {CustomEvent} event The enable-dbl event dispatched by
428        *    kb-shift-key.
429        */
430       enableSel: function(event) {
431         // TODO(rsadam): Disabled for now. May come back if we revert swipe
432         // selection to not do word selection.
433       },
434
435       /**
436        * Handles pointerdown event. This is used for swipe selection process.
437        * to get the start pre_x and pre_y. And also add a pointermove handler
438        * to start handling the swipe selection event.
439        * @param {PointerEvent} event The pointerup event that received by
440        *     kb-keyboard.
441        */
442       down: function(event) {
443         if (event.isPrimary) {
444           swipeStatus.pre_x = event.screenX;
445           swipeStatus.pre_y = event.screenY;
446           this.addEventListener("pointermove", this.swipeHandler, false);
447         }
448       },
449
450       /**
451        * Handles pointerup event. This is used for double tap/click events.
452        * @param {PointerEvent} event The pointerup event that bubbled to
453        *     kb-keyboard.
454        */
455       up: function(event) {
456         // When touch typing, it is very possible that finger moves slightly out
457         // of the key area before releases. The key should not be dropped in
458         // this case.
459         if (this.lastPressedKey &&
460             this.lastPressedKey.pointerId == event.pointerId) {
461           this.lastPressedKey.autoRelease();
462         }
463
464         if (this.dblDetail_) {
465           this.dblDetail_.clickCount++;
466           if (this.dblDetail_.clickCount == 2) {
467             this.dblDetail_.callback();
468             this.changeKeyset(this.dblDetail_);
469             clearTimeout(this.dblTimer_);
470
471             this.classList.add('caps-locked');
472
473             this.dblDetail_ = null;
474           }
475         }
476
477         // TODO(zyaozhujun): There are some edge cases to deal with later.
478         // (for instance, what if a second finger trigger a down and up
479         // event sequence while swiping).
480         // When pointer up from the screen, a swipe selection session finished,
481         // all the data should be reset to prepare for the next session.
482         if (event.isPrimary && swipeInProgress) {
483           swipeInProgress = false;
484           swipeStatus.resetAll();
485         }
486         this.removeEventListener('pointermove', this.swipeHandler, false);
487       },
488
489       /**
490        * Handles key-up event that is sent by kb-key-base.
491        * @param {CustomEvent} event The key-up event dispatched by kb-key-base.
492        * @param {Object} detail The detail of pressed kb-key.
493        */
494       keyUp: function(event, detail) {
495         if (this.skipEvent(detail))
496           return;
497         if (swipeInProgress)
498           return;
499         if (detail.activeModifier) {
500           var modifier = detail.activeModifier.toLowerCase() + "-active";
501           this.classList.add(modifier);
502         }
503         // Adds the current keyboard modifiers to the detail.
504         if (this.ctrl)
505           detail.controlModifier = this.ctrl.isActive();
506         if (this.alt)
507           detail.altModifier = this.alt.isActive();
508         if (this.lastPressedKey)
509           this.lastPressedKey.classList.remove('active');
510         // Keyset transition key. This is needed to transition from upper
511         // to lower case when we are not in caps mode, as well as when
512         // we're ending chording.
513         this.changeKeyset(detail);
514
515         if (this.lastPressedKey &&
516             this.lastPressedKey.charValue != event.target.charValue) {
517           return;
518         }
519         if (repeatKey.key == event.target) {
520           repeatKey.cancel();
521           this.lastPressedKey = null;
522           return;
523         }
524         var toLayoutId = detail.toLayout;
525         // Layout transition key.
526         if (toLayoutId)
527           this.layout = toLayoutId;
528         var char = detail.char;
529         if (enterUpperOnSpace) {
530           enterUpperOnSpace = false;
531           if (char == ' ') {
532             // If shift key defined in layout.
533             if (this.shift) {
534               var shiftDetail = this.shift.onSpaceAfterPunctuation();
535               // Check if transition defined.
536               this.changeKeyset(shiftDetail);
537             } else {
538               console.error('Capitalization on space after punctuation \
539                             enabled, but cannot find target keyset.');
540             }
541           }
542         }
543         this.lastPressedKey = null;
544         switch(char) {
545           case 'Invalid':
546           case 'Shift':
547           case 'Ctrl':
548           case 'Alt':
549             swipeStatus.swipeFlags = 0;
550             return;
551           case 'Microphone':
552             this.voiceInput_.onDown();
553             return;
554           case '.':
555           case '?':
556           case '!':
557             enterUpperOnSpace = true;
558             break;
559           default:
560             break;
561         }
562         if(!this.keyTyped(detail))
563           insertText(char);
564         this.onNonControlKeyTyped();
565       },
566
567       /*
568        * Handles key-longpress event that is sent by kb-key-base.
569        * @param {CustomEvent} event The key-longpress event dispatched by
570        *     kb-key-base.
571        * @param {Object} detail The detail of pressed key.
572        */
573       keyLongpress: function(event, detail) {
574         // If the gesture is long press, remove the pointermove listener.
575         this.removeEventListener('pointermove', this.swipeHandler, false);
576         // Keyset transtion key.
577         if (this.changeKeyset(detail)) {
578           // Locks the keyset before removing active to prevent flicker.
579           this.classList.add('caps-locked');
580           // Makes last pressed key inactive if transit to a new keyset on long
581           // press.
582           if (this.lastPressedKey)
583             this.lastPressedKey.classList.remove('active');
584         }
585       },
586
587       /**
588        * Show menu for selecting a keyboard layout.
589        * @param {!Event} event The triggering event.
590        * @param {{left: number, top: number, width: number}} details Location of
591        *     the button that triggered the popup.
592        */
593       showOptions: function(event, details) {
594         var overlay = this.$.overlay;
595         if (!overlay) {
596           console.error('Missing overlay.');
597           return;
598         }
599         var menu = overlay.$.options;
600         if (!menu) {
601            console.error('Missing options menu.');
602         }
603
604         menu.hidden = false;
605         overlay.hidden = false;
606         var left = details.left + details.width - menu.clientWidth;
607         var top = details.top - menu.clientHeight;
608         menu.style.left = left + 'px';
609         menu.style.top = top + 'px';
610       },
611
612       /**
613        * Handler for the 'set-layout' event.
614        * @param {!Event} event The triggering event.
615        * @param {{layout: string}} details Details of the event, which contains
616        *     the name of the layout to activate.
617        */
618       setLayout: function(event, details) {
619         this.layout = details.layout;
620       },
621
622       /**
623        * Handles a change in the keyboard layout. Auto-selects the default
624        * keyset for the new layout.
625        */
626       layoutChanged: function() {
627         if (!this.selectDefaultKeyset()) {
628           this.isReady = false;
629           this.fire('stateChange', {state: 'loadingKeyset'});
630
631           // Keyset selection fails if the keysets have not been loaded yet.
632           var keysets = document.querySelector('#' + this.layout);
633           if (keysets) {
634             keyboard.appendChild(flattenKeysets(keysets.content));
635             this.selectDefaultKeyset();
636           } else {
637             // Add link for the keysets if missing from the document. Force
638             // a layout change after resolving the import of the link.
639             var query = 'link[id=' + this.layout + ']';
640             if (!document.querySelector(query)) {
641               // Layout has not beeen loaded yet.
642               var link = document.createElement('link');
643               link.id = this.layout;
644               link.setAttribute('rel', 'import');
645               link.setAttribute('href', 'layouts/' + this.layout + '.html');
646               document.head.appendChild(link);
647
648               // Load content for the new link element.
649               var self = this;
650               HTMLImports.importer.load(document, function() {
651                 HTMLImports.parser.parseLink(link);
652                 self.layoutChanged();
653               });
654             }
655           }
656         }
657       },
658
659       /**
660        * Notifies the modifier keys that a non-control key was typed. This
661        * lets them reset sticky behaviour. A non-control key is defined as
662        * any key that is not Control, Alt, or Shift.
663        */
664       onNonControlKeyTyped: function() {
665         if (this.shift)
666           this.shift.onNonControlKeyTyped();
667         if (this.ctrl)
668           this.ctrl.onNonControlKeyTyped();
669         if (this.alt)
670           this.alt.onNonControlKeyTyped();
671         this.classList.remove('ctrl-active');
672         this.classList.remove('alt-active');
673       },
674
675       /**
676        * Indicate if the keyboard is ready for user input.
677        * @type {boolean}
678        */
679       get initialized() {
680         return this.isReady;
681       },
682
683       /**
684        * Id for the active keyset.
685        * @type {string}
686        */
687       get activeKeysetId() {
688         return this.layout + '-' + this.keyset;
689       },
690
691       /**
692        * The active keyset DOM object.
693        * @type {kb-keyset}
694        */
695       get activeKeyset() {
696         return this.querySelector('#' + this.activeKeysetId);
697       },
698
699       /**
700        * The current input type.
701        * @type {string}
702        */
703       get inputTypeValue() {
704         return inputType;
705       },
706
707       /**
708        * Changes the input type if it's different from the current
709        * type, else resets the keyset to the default keyset.
710        * @type {string}
711        */
712       set inputTypeValue(value) {
713         if (value == this.inputType)
714           this.selectDefaultKeyset();
715         else
716           this.inputType = value;
717       },
718
719       /**
720        * Generates fabricated key events to simulate typing on a
721        * physical keyboard.
722        * @param {Object} detail Attributes of the key being typed.
723        * @return {boolean} Whether the key type succeeded.
724        */
725       keyTyped: function(detail) {
726         var builder = this.$.keyCodeMetadata;
727         if (this.shift)
728           detail.shiftModifier = this.shift.isActive();
729         if (this.ctrl)
730           detail.controlModifier = this.ctrl.isActive();
731         if (this.alt)
732           detail.altModifier = this.alt.isActive();
733         var downEvent = builder.createVirtualKeyEvent(detail, "keydown");
734         if (downEvent) {
735           sendKeyEvent(downEvent);
736           sendKeyEvent(builder.createVirtualKeyEvent(detail, "keyup"));
737           return true;
738         }
739         return false;
740       },
741
742       /**
743        * Selects the default keyset for a layout.
744        * @return {boolean} True if successful. This method can fail if the
745        *     keysets corresponding to the layout have not been injected.
746        */
747       selectDefaultKeyset: function() {
748         var keysets = this.querySelectorAll('kb-keyset');
749         // Full name of the keyset is of the form 'layout-keyset'.
750         var regex = new RegExp('^' + this.layout + '-(.+)');
751         var keysetsLoaded = false;
752         for (var i = 0; i < keysets.length; i++) {
753           var matches = keysets[i].id.match(regex);
754           if (matches && matches.length == REGEX_MATCH_COUNT) {
755              keysetsLoaded = true;
756              if (keysets[i].isDefault) {
757                this.keyset = matches[REGEX_KEYSET_INDEX];
758                this.classList.remove('caps-locked');
759                this.classList.remove('alt-active');
760                this.classList.remove('ctrl-active');
761                // Caches shift key.
762                this.shift = this.querySelector('kb-shift-key');
763                if (this.shift)
764                  this.shift.reset();
765                // Caches control key.
766                this.ctrl = this.querySelector('kb-modifier-key[char=Ctrl');
767                if (this.ctrl)
768                  this.ctrl.reset();
769                // Caches alt key.
770                this.alt = this.querySelector('kb-modifier-key[char=Alt');
771                if (this.alt)
772                  this.alt.reset();
773                this.isReady = true;
774                this.fire('stateChange', {
775                  state: 'keysetLoaded',
776                  keyset: this.keyset,
777                });
778                keyboardLoaded();
779                return true;
780              }
781           }
782         }
783         if (keysetsLoaded)
784           console.error('No default keyset found for ' + this.layout);
785         return false;
786       }
787     });
788   </script>
789 </polymer-element>