2 * Copyright 2017 Samsung Electronics Co., Ltd.
4 * Licensed under the Flora License, Version 1.1 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://floralicense.org/license/
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
20 * This source code is a base script of web voice touch.
24 * vc_docs holds analysed documents' first hint number and last hint number
25 * @member doc document elements.
26 * @member start the first tooltip number in @doc.
27 * @member end the last tooltip number in @doc.
31 vc_conflict_timer = null,
34 * vc_conflict_links holds references to links that match the same keyword
36 vc_conflict_links = [];
39 * vc_scr holds info about screen and website
40 * @member top coordinate of the top boundary.
41 * @member left coordinate of the left boundary.
42 * @member bottom coordinate of the bottom boundary.
43 * @member right coordinate of the right boundary.
48 bottom : window.innerHeight,
49 right : window.innerWidth
53 * vc_xpath_query is a query that processes DOM tree to get clickable objects.
54 * It analyses HTML tree as it is XML and returns matching objects in one of selected forms.
56 vc_xpath_query = ".//xhtml:input[not(@type='hidden')] | .//xhtml:a | .//xhtml:area | .//xhtml:textarea | .//xhtml:button | .//xhtml:*[" +
57 "@role='link' or @role='button' or @role='checkbox' or @role='combobox' or @role='radio'] |" +
58 " .//xhtml:div[contains(@class, 'ui-btn')] | .//xhtml:div[contains(@class, 'sideBarButton')] | .//xhtml:div[contains(@class, 'buttonCaption')]",
61 * vc_page_hints holds all hints on website
66 * vc_hint_div is a DIV that gathers all hints
71 * vc_high_div is a DIV that gathers red highlight boxes
76 * vc_hint_num is used to numerate hints from 1 on every screen they're displayed
81 * vc_visible_hints holds all hints that are currently visible
83 vc_visible_hints = [],
86 * vc_bad_iframes holds iframes that should be highlighted with red color
91 * vc_log_area is a textarea element for log texts.
93 vc_log_area = document.createElement('textarea'),
96 * vc_html_area is a textarea element to read raw source code of html document in TV.
98 vc_html_area = undefined,
101 * vc_asr_result is a div element shows the asr result for debugging.
103 vc_asr_result = undefined,
106 * vc_rec_result is a div element shows the recognition result of document searching for debugging.
108 vc_rec_result = undefined,
111 * vc_flag_log is a flag variable. If it is true, vc_log_area is created. Otherwise, vc_log_area is nothing.
116 * vc_all_elem is a array that includes whole element in html document for text searching.
118 vc_all_elem = undefined,
121 * vc_flag_conflict is a flag variable. When multiple elements are selected, it is true. Otherwise, it is false.
123 vc_flag_conflict = false,
126 * vc_flag_hint_exist is a flag variable. When tooltips are already exist, it is true. Otherwise, it is false.
128 vc_flag_hint_exist = false,
131 * vc_flag_make_tooltip is a flag variable. When creating tooltip is set, it is true. Otherwise, it is false.
133 vc_flag_make_tooltip = true,
136 * vc_text_indicators is an array that contains text indicator elements
138 vc_text_indicators = [],
141 * vc_made_hint shows the state tooltip making.
143 vc_made_hint = false,
146 * vc_tag_index stores index number of the set of tooltips in web page.
147 * this variable prevents making duplicated tooltips.
152 * prototypes of overridable custom function
154 function vc_custom_pre_generate_hints() {
155 vc_print_log('No custom script');
158 function vc_custom_pre_show_hints() {
159 vc_print_log('No custom script');
162 function vc_custom_pre_remove_hints() {
163 vc_print_log('No custom script');
166 function vc_custom_pre_hide_hints() {
167 vc_print_log('No custom script');
169 /* ======================== */
172 * vc_custom_add_xpath_query functino adds given condition to xpath query
174 * @param className the name of the class of DOM elements
175 * @param DOMtype(optional) the type name of DOM element
177 * @remind this function must be called in page specific script
179 function vc_custom_add_xpath_query(className, DOMtype) {
180 if (undefined == DOMtype) {
184 vc_xpath_query += " | .//xhtml:" + DOMtype + "[contains(@class, '" + className + "')]";
188 * vc_is_child check if element is a child of other element
190 * @param child child to check the parent of
191 * @param parent to test to
193 * @return true if elements are related, false otherwise
195 function vc_is_child(child, parent) {
196 var node = child.parentNode;
197 while (node && node != document.body) {
198 if (node === parent) {
201 node = node.parentNode;
207 * vc_is_hidden function checks if the element given as parameter is hidden due to its CSS style
209 * @param elem element to be checked
210 * @param predecessor predecessor of elem to check visibility if elementFromPoint doesn't return elem
212 * @return true if element is invisible, false otherwise
214 function vc_is_hidden(elem, predecessor) {
215 if (elem == undefined) {
219 /* if predecesor is no found assume parent is one */
220 if (!predecessor && elem.parentNode) {
221 predecessor = elem.parentNode;
223 /* check visibility in css style */
224 var doc = elem.ownerDocument,
225 win = doc.defaultView,
226 computedStyle = win.getComputedStyle(elem, null);
228 if (computedStyle.getPropertyValue('visibility') !== 'visible' || computedStyle.getPropertyValue('display') === 'none') {
232 /* object is visible check if it is on current screen */
233 var rect = elem.getBoundingClientRect(),
234 x = rect.left + (rect.width / 2),
235 y = rect.top + (rect.height / 2),
238 if (y < 0 && x >= 0) {
240 } else if (y < 0 && x < 0) {
242 } else if (y >= 0 && x < 0) {
246 point = doc.elementFromPoint(x, y);
247 /* if elementFromPoint returns HTMLBodyElement it probably is a scroll left/right list and it is hidden */
248 if (point === doc.body) {
251 /* if elementFromPoint returns predecessor it is visible */
252 if (point && predecessor && vc_is_child(point, predecessor)) {
260 * vc_is_visible function checks if the element provided as argument is currently visible on screen
262 * @param elem element to be checked
263 * @param scr screen information
264 * @param isTextLink determines if the element is text link
266 * @return true if visible false otherwise
268 function vc_is_visible(elem, scr, isTextLink) {
269 if (elem == undefined) {
277 bottom : window.innerHeight,
278 right : window.innerWidth
281 // if only two parameters passed to function assume isTextLink is false
282 isTextLink = typeof isTextLink !== 'undefined' ? isTextLink : false;
284 var docy = elem.ownerDocument,
285 win = docy.defaultView,
286 computedStyle = win.getComputedStyle(elem, null);
288 /* element is not visible if it is fully transparent except select box */
289 if (parseFloat(computedStyle.getPropertyValue('opacity')) === 0.0) {
293 var rect = elem.getBoundingClientRect();
295 /* element is not visible if it is not on screen */
296 if (!rect || rect.top >= scr.bottom || rect.bottom <= scr.top || rect.left >= scr.right ||
297 rect.right <= scr.left) {
301 /* element is not visible if it is hidden and it is not a text hyperlink */
302 if (!isTextLink && vc_is_hidden(elem, null)) {
310 * vc_make_hint procedure creates invisible hint for element provided as argument
312 * @param elem element for which hint is to be created
313 * @param child if child is passed, the hint will be positioned according to position of child
315 function vc_make_hint(elem, child) {
316 // vc_print_log(elem);
317 if (false == vc_flag_make_tooltip) {
323 rect = child.getBoundingClientRect();
325 if (!rect || elem.vc_tag_index == vc_tag_index) {
329 elem.vc_tag_index = vc_tag_index;
331 var doc = elem.ownerDocument;
333 /** hint object holds clickable element, type of element and created hint (HTMLSpanElement)
334 * @member elem clickable target element.
335 * @member type the type of @elem.
336 * @member child child element of @elem
337 * @member span tooltip element of @elem
346 if(elem instanceof HTMLAreaElement){
350 /* not all input objects are supported */
351 if (elem instanceof HTMLInputElement) {
352 if (elem.type === 'text' || elem.type === 'password' || elem.type === 'datetime' || elem.type === 'email' ||
353 elem.type === 'search' || elem.type === 'number' || elem.type === 'url') {
355 } else if (elem.type !== 'button' && elem.type !== 'submit' && elem.type !== 'color' && elem.type !== 'radio' &&
356 elem.type !== 'checkbox' && elem.type !== 'file' && elem.type !== 'submit' && elem.type !== 'image') {
359 /* consider TextArea element as input element */
360 } else if (elem instanceof HTMLTextAreaElement) {
364 /** span object is the visual representation of hint */
365 var span = doc.createElement('span');
366 span.id = 'vc_tooltip';
367 span.className = 'vc_number_tag_normal vc_tooltip_' + hint.type;
369 if (hint.type == 'input') {
370 span.classList.add('vc_tip_tier_first');
371 } else if (rect.height > 30) {
372 span.classList.add('vc_tip_tier_second');
374 span.classList.add('vc_tip_tier_third');
379 /* appends hint to vc_hint_div container */
380 vc_hint_div.appendChild(hint.span);
381 vc_page_hints.push(hint);
385 * nsResolver function that is used for resolving namespace in webpage document
387 * @param prefix prefix of the namespace
389 * @return namespace url
391 function nsResolver(prefix) {
393 'xhtml' : 'http://www.w3.org/1999/xhtml' //add other namespaces if needed
395 return namespace[prefix] || null;
399 * vc_generate_hints function creates hints for all clickable elements on web page
401 * @param win window element (can be different if we analyse iframe)
403 function vc_generate_hints(win) {
404 vc_print_log('== start generate hints' + win);
407 /* if win is not passed assume the global window variable */
417 vc_print_log('== some error occured in win.document' + e);
421 /* Custom pre process to generate hints */
422 vc_custom_pre_generate_hints();
424 var start = vc_page_hints.length;
426 vc_hint_div = doc.createElement('div');
427 vc_hint_div.id = 'vc_tooltip_area';
428 vc_hint_div.style.display = 'block';
430 vc_text_indicators = [];
432 /* return result as snapshot not iterator, due to problems with undefined objects */
433 vc_print_log('== start doc.evaluate');
434 var snapshot = doc.evaluate(vc_xpath_query, doc.body, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
435 vc_print_log('== snapshot length : ' + snapshot.snapshotLength);
437 /* set current screen dimensions */
441 bottom : win.innerHeight,
442 right : win.innerWidth
445 /* analyse if a hint is needed for each returned object */
446 for (var i = 0; i < snapshot.snapshotLength; i++) {
447 var element = snapshot.snapshotItem(i),
448 rect = element.getBoundingClientRect();
450 vc_made_hint = false;
451 /* executed very rarely */
452 if (rect.width === 0 || rect.height === 0) {
453 for (var j = 0; j < element.childNodes.length; j++) {
454 if (element.childNodes[j].nodeType !== 1) {
458 var style = doc.defaultView.getComputedStyle(element.childNodes[j], null);
459 if (style && style.getPropertyValue('float') !== 'none' && vc_is_visible(element.childNodes[j], vc_scr)) {
460 vc_make_hint(element, element.childNodes[j]);
471 if (vc_is_visible(element, vc_scr) && !element.attributes.disabled) {
472 if ((element instanceof HTMLButtonElement || element.classList.contains('ui-btn') || element.getAttribute('role') == 'button')) {
473 if (element.textContent.trim().replace(/[ `~!‘’@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '').length > 0) {
474 vc_text_indicators[vc_text_indicators.length] = element;
476 vc_make_hint(element, element);
479 } else if (element.getAttribute('role') == 'radio' || element.getAttribute('role') == 'checkbox' || element.getAttribute('role') == 'combobox') {
480 vc_make_hint(element, element);
482 /* if the element is an anchor without text, make hint */
483 } else if (element instanceof HTMLAnchorElement) {
484 if (element.textContent.trim().length > 0) {
485 vc_text_indicators[vc_text_indicators.length] = element;
488 var cStyle = window.getComputedStyle(element, false);
489 if (cStyle.backgroundImage !== 'none' && cStyle.backgroundImage !== 'inherit') {
490 vc_make_hint(element, element);
492 } else if (element.textContent.trim().length == 0) {
493 /* if element has child with background or image element*/
495 var childs = element.querySelectorAll('*');
496 for (var sp = 0; sp < childs.length; ++sp) {
497 if (vc_is_visible(childs[sp], vc_scr)) {
498 var cstyle = win.getComputedStyle(childs[sp], false);
499 if (childs[sp].nodeName == 'IMG' || cstyle.overflow == 'hidden'
500 || (cstyle.backgroundImage !== 'none' && cstyle.backgroundImage !== 'inherit')) {
501 vc_make_hint(element, childs[sp]);
510 vc_make_hint(element, element);
515 if (isNaN(element.textContent.trim())) {
516 vc_find_img_element(element, null, element);
519 } else if (element instanceof HTMLAreaElement || element.nodeName == 'INPUT') {
520 vc_make_hint(element, element);
522 vc_find_img_element(element, null, element);
523 if (vc_made_hint == false) {
524 vc_make_hint(element, element);
530 /* append whole div with hints to body */
532 doc.body.insertBefore(vc_hint_div, doc.body.firstChild);
534 //append all text indicators to body
538 end : vc_page_hints.length - 1
542 /* analyse iframes of the website */
543 if (win === window) {
544 var frames = document.querySelectorAll('iframe');
545 for (var f = 0; f < frames.length; f++) {
548 elem = frames[f].contentWindow.document;
550 vc_bad_iframes.push(frames[f]);
556 bottom : win.innerHeight,
557 right : win.innerWidth
559 if (elem == undefined) {
560 vc_bad_iframes.push(frames[f]);
563 if (elem != undefined && !vc_is_visible(frames[f], vc_scr)) {
566 // vc_generate_hints(frames[f].contentWindow);
570 vc_flag_hint_exist = true;
571 vc_print_log('== end');
575 * vc_find_img_element function find element containing image. if found then call a function to make a tooltip.
577 * @param elem element to search for text
578 * @param parent parent element of the @elem
579 * @param snapshotElem the first @elem when vc_generate_hints calls this function
581 function vc_find_img_element(elem, parent, snapshotElem) {
583 if (elem.nodeType == 1) {
584 var cStyle = document.defaultView.getComputedStyle(elem, false);
585 if ((vc_is_visible(elem, vc_scr) && cStyle && cStyle.backgroundImage != 'none' && cStyle.backgroundImage != 'inherit' || elem.nodeName == 'IMG')) {
586 vc_make_hint(snapshotElem, elem);
592 var children = elem.childNodes;
593 for (var ch = 0; !vc_made_hint && ch < children.length; ch++) {
594 vc_find_img_element(children[ch], elem, snapshotElem);
601 * vc_show_hints function displays hints numbering from 1
603 function vc_show_hints() {
604 /* Custom pre process to show hints */
605 vc_custom_pre_show_hints();
607 if (vc_flag_log == true) {
608 if (vc_asr_result == undefined) {
609 vc_asr_result = document.createElement('div');
610 vc_asr_result.id = 'vc_asr_result';
611 vc_asr_result.innerHTML = 'ASR Result';
612 document.body.appendChild(vc_asr_result);
615 if (vc_rec_result == undefined) {
616 vc_rec_result = document.createElement('div');
617 vc_rec_result.id = 'vc_rec_result';
618 document.body.appendChild(vc_rec_result);
623 for (var d = 0; d < vc_docs.length; d++) {
624 var doc = vc_docs[d];
625 for (var i = doc.start; i <= doc.end; i++) {
626 var win = doc.doc.defaultView,
627 hint = vc_page_hints[i],
628 rect = hint.child.getClientRects()[0];
633 bottom : win.innerHeight,
634 right : win.innerWidth
637 hint.span.style.display = 'block';
646 position_factor_left;
648 if (vc_flag_conflict) {
649 position_factor_top = 18;
650 position_factor_left = 18;
651 hint.span.className = 'vc_text_indicator_conflict_normal vc_tooltip_normal vc_tip_tier_second';
653 position_factor_top = 15;
654 position_factor_left = 15;
657 if (rect.top < position_factor_top) {
658 topPos = win.pageYOffset;
660 topPos = rect.top + win.pageYOffset - position_factor_top;
663 if (rect.left < position_factor_left) {
664 leftPos = win.pageXOffset;
666 leftPos = rect.left + win.pageXOffset - position_factor_left;
669 if (hint.type == 'area') {
670 coord = hint.elem.coords.split(',');
671 leftPos = parseFloat(leftPos) + parseFloat(coord[0]);
672 topPos = parseFloat(topPos) + parseFloat(coord[1]);
674 hint.span.style.top = topPos + 'px';
675 hint.span.style.left = leftPos + 'px';
676 hint.span.textContent = vc_hint_num;
678 hint.span.style.display = 'block';
680 if (-1 == vc_visible_hints.indexOf(hint)) {
681 vc_visible_hints.push(hint);
688 // for (var bf = 0; bf < vc_bad_iframes.length; ++bf) {
689 // vc_result_highlight(vc_bad_iframes[bf]);
694 * vc_remove_hints function removes all hints in the web page. It deletes both visible and hidden hints
696 function vc_remove_hints() {
697 /* Custom pre process to remove hints */
698 vc_custom_pre_remove_hints();
700 vc_remove_highlight();
702 for (var d = 0; d < vc_docs.length; d++) {
703 var doc = vc_docs[d].doc;
704 var tooltipArea = doc.querySelector('#vc_tooltip_area');
705 if (null !== tooltipArea && tooltipArea.parentNode) {
706 tooltipArea.parentNode.removeChild(tooltipArea);
709 vc_visible_hints = [];
713 /** if the rotation occured refresh the window information */
717 bottom : window.innerHeight,
718 right : window.innerWidth
722 vc_flag_hint_exist = false;
723 vc_flag_conflict = false;
727 * vc_hide_hints function hides the hints from the screen. Hints elements still exist
729 function vc_hide_hints(focusedExist) {
730 /* Custom pre process to hide hints */
731 if (true != focusedExist) {
732 vc_custom_pre_hide_hints();
735 for (var i = 0; i < vc_visible_hints.length; i++) {
736 var hint = vc_visible_hints[i];
737 if (hint.span.classList.contains('vc_tooltip_focus') == false) {
738 hint.span.style.display = 'none';
739 hint.span.style.top = 0;
740 hint.span.style.left = 0;
746 * vc_remove_popup function removes popup.
747 * This function is safe to call if the popup doesnt exist.
749 function vc_remove_popup() {
750 var node = document.querySelector('#vc_popup');
751 if (null !== node && node.parentNode) {
752 node.parentNode.removeChild(node);
757 * vc_show_popup function displays a floating div over the web page with text in it for timout milliseconds
759 * @param text text to be shown on the popup
760 * @param timeout time for which the popup should be shown in milliseconds. If timeout is 0 popup must be deleted manually
762 function vc_show_popup(text, timeout) {
763 /* remove any existing popup */
766 /* overlay creates a light grey area over the whole website */
767 var overlay = document.createElement('div');
768 overlay.id = 'vc_popup';
770 /* vcpopup is the main popup window, that is centered over the overlay */
771 var vcpopup = document.createElement('div');
772 vcpopup.id = 'vc_popup_content';
774 /* content is used to display text and center it vertically inside vcpopup */
775 var content = document.createElement('div');
776 content.style.display = 'table-cell';
777 content.style.fontFamily = '"Open Sans", "Helvetica", sans-serif';
778 content.style.verticalAlign = 'middle';
779 content.innerHTML = text;
781 vcpopup.appendChild(content);
782 overlay.appendChild(vcpopup);
783 document.body.appendChild(overlay);
785 /* if timeout is set to 0, display popup until not deleted manually */
787 setTimeout(function () {
796 * vc_remove_highlight removes highlight from conflicting links
798 * @param selectedNum the number of target tooltip
800 function vc_remove_highlight(selectedNum) {
802 var high = document.querySelectorAll('#vc_highlight');
803 for (var cn = 0; cn < high.length; cn++) {
804 if ((cn + 1) != selectedNum) {
805 high[cn].parentNode.removeChild(high[cn]);
809 var highlightArea = document.querySelector('#vc_highlight_area');
811 highlightArea.parentNode.removeChild(highlightArea);
817 * vc_result_highlight creates highlight box over elem
819 * @param elem element to be highlighted
821 function vc_result_highlight(elem) {
822 var doc = elem.ownerDocument,
825 if (vc_is_visible(elem) == false && elem.querySelector('img')) {
826 rect = elem.querySelector('img').getClientRects()[0];
828 rect = elem.getClientRects()[0];
835 vc_high_div = document.querySelector('#vc_highlight_area');
838 vc_high_div = doc.createElement('div');
839 vc_high_div.id = 'vc_highlight_area';
840 doc.body.appendChild(vc_high_div);
843 var high_span = doc.createElement('div');
844 high_span.id = 'vc_highlight';
846 /* top left position of the element */
847 var leftPos = (rect.left > vc_scr.left ? rect.left : vc_scr.left);
848 var topPos = (rect.top > vc_scr.top ? rect.top : vc_scr.top);
850 /* adjust top left position with page scroll */
851 topPos += window.pageYOffset - 3;
852 leftPos += window.pageXOffset - 3;
854 high_span.style.left = leftPos + 'px';
855 high_span.style.top = topPos + 'px';
856 high_span.style.width = rect.width + 'px';
857 high_span.style.height = (rect.height + 6) + 'px';
859 /* if element's dimensions are 0x0 create highlight for it's children */
860 if (rect.width === 0 || rect.height === 0) {
861 var height = topPos + rect.height;
862 var width = leftPos + rect.width;
863 for (var i = 0; i < elem.children.length; i++) {
864 var child = elem.children[i];
865 var chRect = child.getClientRects()[0];
869 if (chRect.top + chRect.height > height) {
870 height = chRect.top + chRect.height;
872 if (chRect.left + chRect.width > width) {
873 width = chRect.left + chRect.width;
876 high_span.style.height = height - topPos + 'px';
877 high_span.style.width = width - leftPos + 'px';
880 vc_high_div.appendChild(high_span);
884 * vc_generate_conflict_hints function creates hints for all conflicting highlighted hints
886 function vc_generate_conflict_hints() {
887 vc_hint_div = document.querySelector('#vc_tooltip_area');
890 vc_hint_div = document.createElement('div');
891 vc_hint_div.id = 'vc_tooltip_area';
892 document.body.insertBefore(vc_hint_div, document.body.firstChild);
898 bottom : window.innerHeight,
899 right : window.innerWidth
903 var conflicts = vc_conflict_links;
905 for (var i = 0; i < conflicts.length; i++) {
906 var elem = conflicts[i];
909 vc_result_highlight(elem);
910 if (vc_is_visible(elem, vc_scr) == false && elem.querySelector('img') != null) {
911 rect = elem.querySelector('img').getClientRects()[0];
913 rect = elem.getClientRects()[0];
922 position_factor_top = 20, //[TODO]move at proper place
923 position_factor_left = 28,
925 span = document.createElement('span');
926 span.id = 'vc_tooltip';
928 if (rect.top < position_factor_top) {
929 topPos = window.pageYOffset + 'px';
931 topPos = rect.top + window.pageYOffset - position_factor_top + 'px';
934 if (rect.left < position_factor_left) {
935 leftPos = window.pageXOffset + 'px';
937 leftPos = rect.left + window.pageXOffset - position_factor_left + 'px';
940 /* tooltip style, !please keep styles sorted */
942 hint.span.style.top = topPos;
943 hint.span.style.left = leftPos;
944 vc_hint_div.appendChild(hint.span);
947 hint.child = vc_high_div.childNodes[i];
948 vc_page_hints[vc_page_hints.length] = hint;
954 end : vc_page_hints.length - 1
957 vc_flag_hint_exist = true;
958 vc_flag_conflict = true;
959 vc_conflict_timer = setTimeout(vc_remove_hints, 5000);
965 * vc_trigger_event function makes custom DOM event
967 * @param node target element taking event
968 * @param eventType type of the custom event
970 function vc_trigger_event(node, eventType) {
972 var clickEvent = new Event(eventType);
973 node.dispatchEvent(clickEvent);
975 console.log('error ouccured', e);
980 * vc_do_action function creates event for selected element.
982 * @param targetElem selected element
983 * @param numberTag number of selected tooltip
984 * @param value(option) text entered into selected input element
986 * @return array including coordinates of the @targetElem and the number of targetElem.
988 function vc_do_action(targetElem, numberTag, value) {
989 vc_print_log('=== Inside vc_do_action');
990 vc_print_log(targetElem.elem.outerHTML);
992 var elem = targetElem.elem,
993 type = targetElem.type,
1001 /* Create click effect element */
1002 effect = document.createElement('div');
1003 effect.id = 'vc_click_effect';
1005 if (numberTag != undefined) {
1006 position = numberTag.getBoundingClientRect();
1007 effect.style.left = (position.left + (position.width / 2) - 35) + 'px'
1008 effect.style.top = (position.top + (position.height / 2) - 35) + 'px'
1010 numberTag.classList.add('vc_tooltip_focus');
1011 numberTag.style.display = 'block';
1014 if (vc_flag_conflict) {
1015 vc_remove_highlight(numberTag.textContent.trim());
1017 vc_hide_hints(true);
1019 position = elem.getClientRects()[0];
1020 effect.style.left = (position.left - 35) + 'px'
1021 effect.style.top = (position.top - 35) + 'px'
1024 vc_result_highlight(elem);
1025 elem.classList.add('vc_text_focus');
1027 vc_text_indicators[vc_text_indicators.length] = elem;
1030 document.body.insertBefore(effect, document.body.firstChild);
1032 /* Occurring events */
1034 if (type == 'input' && value != undefined) {
1035 setTimeout(function () {
1037 vc_trigger_event(elem, 'keyup');
1038 vc_trigger_event(elem, 'input');
1045 document.body.removeChild(effect);
1052 setTimeout(function () {
1053 if (vc_asr_result != undefined) {
1054 vc_asr_result.parentElement.removeChild(vc_asr_result);
1055 vc_asr_result = undefined;
1057 if (vc_rec_result != undefined) {
1058 vc_rec_result.parentElement.removeChild(vc_rec_result);
1059 vc_rec_result = undefined;
1062 if (numberTag == undefined) {
1063 elem.classList.remove('vc_text_focus');
1066 document.body.removeChild(effect);
1072 vc_print_log('=== Click Finish');
1075 var rect = elem.getClientRects()[0],
1076 x = rect.left + (rect.width / 2),
1077 y = rect.top + (rect.height / 2);
1079 vc_print_log('[ERROR] Target element is not valid')
1089 return [1, x / window.innerWidth, y / window.innerHeight];
1097 * vc_click function triggers click on element connected to cmd and param. Assumption is made that tooltips are numbers!
1099 * @param cmd tooltip tag number from voice-control
1100 * @param param text parameter from voice-control
1102 function vc_click(cmd, param) {
1103 vc_print_log('== start click cmd = ' + cmd + ', param = ' + param + ', visible tags = ' + vc_visible_hints.length);
1105 if (vc_flag_log == true) {
1106 if (vc_asr_result != undefined) {
1107 vc_asr_result.innerHTML = param;
1110 setTimeout(function () {
1111 if (vc_asr_result != undefined) {
1112 vc_asr_result.parentElement.removeChild(vc_asr_result);
1113 vc_asr_result = undefined;
1115 if (vc_rec_result != undefined) {
1116 vc_rec_result.parentElement.removeChild(vc_rec_result);
1117 vc_rec_result = undefined;
1122 /* pre-defined rule checker */
1123 if (vc_check_web_control(param) == true) {
1129 // if cmd has a numerical value
1130 if (isNaN(cmd) == false) {
1131 vc_print_log('== numbering tag click');
1133 // when cmd is floating point value without parameter
1134 // search exactly the same text
1135 if (Number.isInteger(cmd) == false && param == '') {
1136 for (var i = 0; i < vc_text_indicators.length; ++i) {
1137 if (vc_text_indicators[i].textContent.trim().indexOf(cmd) != -1) {
1138 return vc_do_action({
1139 elem : vc_text_indicators[i]
1144 var list = vc_selector([cmd.toString()]);
1146 for (var i = 0; i < list.length; i++) {
1147 if (list[i].childElementCount == 0 && list[i].textContent.trim().indexOf(cmd) != -1) {
1148 return vc_do_action({
1155 // when cmd is valid integer value
1156 if (Number.isInteger(cmd) == true && cmd <= vc_visible_hints.length) {
1157 var hint = vc_visible_hints[cmd - 1];
1159 // insert text into input element
1160 if (hint.type === 'input' && param != '') {
1161 return vc_do_action(hint, hint.span, param);
1162 } else if (param == '') {
1163 if (!vc_flag_conflict) {
1164 vc_conflict_links = [];
1165 vc_conflict_links.push(hint.elem);
1167 for (var i = 0; i < vc_text_indicators.length; ++i) {
1168 if (vc_text_indicators[i].textContent.trim() == cmd) {
1169 vc_conflict_links.push(vc_text_indicators[i]);
1173 if (vc_conflict_links.length > 1) {
1175 vc_generate_conflict_hints();
1176 return [vc_conflict_links.length, 0, 0];
1180 return vc_do_action(hint, hint.span);
1184 // when there is no matched tooltip
1187 return vc_search_text(cmd.toString().concat(' ', param).trim());
1191 return vc_search_text(param);
1196 * vc_search_text function search the element that include same text with @param.
1198 * @param param param from voice-control.
1200 function vc_search_text(param) {
1201 /* phase 1. search full text in the webpage */
1202 /* first, compare with links in html documents */
1203 vc_print_log('=== start searching(text level)');
1205 if (vc_flag_log == true) {
1206 vc_rec_result.style.background = 'rgba(0, 200, 100, 1)';
1212 bottom : window.innerHeight,
1213 right : window.innerWidth
1216 vc_conflict_links = [];
1218 for (var i = 0; i < vc_text_indicators.length; ++i) {
1219 if (vc_is_visible(vc_text_indicators[i], vc_scr, true) &&
1220 vc_text_indicators[i].textContent.replace(/[ `~!‘’@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '').toLowerCase().indexOf(param.replace(/ /g, '').toLowerCase()) !== -1) {
1221 vc_conflict_links.push(vc_text_indicators[i]);
1225 if (vc_conflict_links.length == 1) {
1226 /* if there is one result */
1227 return vc_do_action({
1228 elem : vc_conflict_links[0]
1230 } else if (vc_conflict_links.length > 1) {
1231 vc_generate_conflict_hints();
1233 return [vc_conflict_links.length, 0, 0];
1236 /* second, compare with whole text of elements in html documents */
1237 var resultTokenArr = param.replace(/[`~!‘’@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '').split(' '),
1238 el = vc_selector(resultTokenArr),
1241 for (var i = 0; el.length > i; i++) {
1244 if (temp.childElementCount === 0) {
1245 var curStr = temp.textContent.replace(/[`~!‘’@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '');
1246 if (curStr == preStr || vc_is_visible(temp) == false) {
1252 vc_conflict_links.push(temp);
1256 if (vc_conflict_links.length == 1) {
1257 return vc_do_action({
1258 elem : vc_conflict_links[0]
1260 } else if (vc_conflict_links.length > 1) {
1261 vc_generate_conflict_hints();
1263 return [vc_conflict_links.length, 0, 0];
1266 * vc_search_word depend on the voice control language.
1267 * This function increase the accuracy of the searching result using word and letter level searching.
1268 * If the language is changed, the definition of the function will be changed by reloading the .js file.
1270 temp = vc_search_word(param, true);
1271 if (temp != undefined) {
1272 return vc_do_action({
1276 //vc_show_popup('There is no such kind of string.<br>(' + param + ')', 1500);
1285 * vc_scroll_event_firing function move the scroll.
1287 * @param evt keyword to move the scroll.
1289 function vc_scroll_event_firing(evt) {
1290 if (evt == 'DOWN') {
1291 window.scrollBy(0, 500);
1292 } else if (evt == 'UP') {
1293 window.scrollBy(0, -500);
1294 } else if (evt == 'TOP') {
1295 window.scrollTo(0, 0);
1300 * vc_print_html function print out log on vc_log_area and console for debug.
1301 * If vc_flag_log is not true, then the logs will only print on console.
1303 * @param text log text to print out.
1305 function vc_print_log(text) {
1307 var time = d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds() + ':' + d.getMilliseconds();
1308 console.log(time, '[', vc_print_log.caller.name, ']', text);
1310 if (vc_flag_log == true) {
1311 vc_log_area.value += time + ' [' + vc_print_log.caller.name + '] ' + text + '\n';
1316 * vc_print_html function print out raw source of the html document for debug.
1318 function vc_print_html() {
1319 if (vc_html_area == undefined) {
1320 vc_html_area = document.createElement('textarea');
1321 vc_html_area.disabled = true;
1322 vc_html_area.id = 'vc_html_area';
1324 document.body.appendChild(vc_html_area);
1326 vc_html_area.value = location.href.toString() + '\n' + document.querySelector('html').innerHTML;
1328 document.body.removeChild(vc_html_area);
1329 vc_html_area = undefined;
1334 * vc_search_text function is called by voice-control-webview.cpp.
1335 * this function call some group of functions refered to @phase.
1337 * @param phase key value to call a group of functions.
1338 * @param data function data from voice-control-webview.cpp.
1340 function vc_request_action(phase, data) {
1341 vc_print_log('phase :' + phase + ',data :' + data);
1342 if ('SHOW_TOOLTIP' == phase) {
1343 clearTimeout(vc_conflict_timer);
1345 /* create & show tooltips to recognize the voice */
1346 if (!vc_flag_conflict && false == vc_flag_hint_exist) {
1348 vc_generate_hints();
1351 } else if ('HIDE_TOOLTIP' == phase) {
1353 if (!vc_flag_conflict) {
1356 clearTimeout(vc_conflict_timer);
1357 vc_conflict_timer = setTimeout(vc_remove_hints, 5000);
1359 } else if ('RESULT' == phase) {
1360 /* do action by the result */
1361 vc_all_elem = document.querySelectorAll('body *:not(script)');
1362 var result = vc_correct_parameter(data);
1364 return vc_click(result.cmd, result.param);
1365 } else if ('REMOVE_TOOLTIP' == phase) {
1366 clearTimeout(vc_conflict_timer);
1375 * vc_selector function is a selector function to find the elements include whole text in @strArr.
1377 * @param strArr group of strings to find elements.
1379 * ex) vc_selector(['abc', 'def']);
1381 function vc_selector(strArr) {
1383 for (var i = 0; i < vc_all_elem.length; i++) {
1384 var temp = vc_all_elem[i].textContent.replace(/[`~!‘’@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '').toLowerCase();
1387 for (j = 0; j < strArr.length; j++) {
1388 if (temp.includes(strArr[j].toLowerCase()) == false) {
1393 if (strArr.length == j && vc_is_visible(vc_all_elem[i], null, true)) {
1394 result.push(vc_all_elem[i]);
1402 * vc_get_url_path function returns url path of the view.
1404 * @return part of the url(host name + path name). it is used to read page specific script.
1406 function vc_get_url_path() {
1407 var hostName = document.location.hostname;
1408 var pathName = document.location.pathname;
1410 pathName = pathName.replace(/\//gi, '.');
1412 return hostName + '/' + pathName;
1416 * vc_get_conflict_status function returns the conflict status.
1418 * @return 1 if vc_flag_conflict is true. Otherwise 0.
1420 function vc_get_conflict_status() {
1421 if (vc_flag_conflict) {
1429 * vc_set_make_tooltip function sets the vc_flag_make_tooltip.
1431 * @param makeTooltip If it is 1, then set vc_flag_make_tooltip as true. Otherwise false.
1433 function vc_set_make_tooltip(makeTooltip) {
1434 if (1 == makeTooltip) {
1435 vc_flag_make_tooltip = true;
1437 vc_flag_make_tooltip = false;
1442 * vc_init function initialize some elements and styles to use voice control.
1444 function vc_init() {
1445 /* log element initialize */
1447 vc_log_area.disabled = true;
1448 vc_log_area.id = 'vc_log_area';
1449 vc_log_area.style.visibility = 'hidden';
1451 document.body.appendChild(vc_log_area);
1454 /* add tooltip style element */
1455 var vc_style_node = document.createElement('style');
1456 var vc_style_text = document.createTextNode(
1457 '#vc_tooltip_area {\
1459 position : absolute;\
1460 pointer-events: none;\
1462 z-index : 2147483647;\
1464 #vc_html_area, #vc_log_area {\
1465 background-color : rgba(0, 0, 0, 0.5) !important;\
1466 color : white !important;\
1470 padding : 10px 10px 10px 10px;\
1474 z-index : 2147483647;\
1476 #vc_popup_content {\
1477 background-color : #87cff7;\
1479 border-radius : 20px;\
1480 box-shadow : 0 2px 6px rgba(0, 0, 0, 0.3), 0 3px 8px rgba(0, 0, 0, 0.2);\
1486 line-height : 1.5em;\
1490 position : absolute;\
1492 text-align : center;\
1495 z-index : 2147483647;\
1498 background-color : rgba(0,0,0,0.2);\
1504 z-index : 2147483647;\
1506 #vc_highlight_area {\
1508 position : absolute;\
1509 pointer-events: none;\
1511 z-index : 2147483646;\
1514 border-radius: 5px;\
1517 position : absolute;\
1518 pointer-events: none;\
1519 word-break : normal;\
1520 background-color: rgba(0, 234, 255, 0.2);\
1523 border-radius: 70px;\
1526 pointer-events: none;\
1528 z-index : 2147483647;\
1529 background: linear-gradient(141deg, rgba(15, 185, 175, 0.6) 0%, rgba(30, 200, 220, 0.6) 51%, rgba(45, 180, 230, 0.6) 75%);\
1531 .vc_number_tag_normal {\
1532 border : 2px solid;\
1533 border-color : rgba(255, 255, 255, 0);\
1534 border-radius : 5px;\
1535 box-shadow : 0px 2px 2px rgba(0, 0, 0, 0.3);\
1536 position: absolute;\
1537 pointer-events: none;\
1538 text-align: center;\
1539 font-family: "Samsung0ne600";\
1541 z-index : 2147483647;\
1542 word-break : normal;\
1544 .vc_text_indicator_conflict_normal {\
1546 border-color: rgba(255, 255, 255, 0);\
1547 border-radius: 20px;\
1548 box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);\
1549 position: absolute;\
1550 pointer-events: none;\
1551 text-align: center;\
1553 font-family: "Samsung0ne600";\
1554 z-index: 2147483647;\
1556 .vc_tooltip_focus {\
1557 border-color: rgba(255, 255, 255, 1);\
1560 color: #ff0000 !important;\
1561 font-weight: bolder !important;\
1564 background : rgba(0, 0, 0, 0.8);\
1565 border-radius : 20px;\
1569 padding-top : 10px;\
1570 padding-bottom : 10px;\
1571 position : absolute;\
1572 text-align : center;\
1576 z-index : 2147483647;\
1579 border-radius : 40px;\
1581 position : absolute;\
1585 z-index : 2147483647;\
1587 .vc_tooltip_input {\
1588 background-color : rgba(60, 140, 250, 0.75);\
1590 .vc_tooltip_normal, .vc_tooltip_area {\
1591 background-color : rgba(60, 185, 165, 0.75);\
1593 .vc_tip_tier_first {\
1599 .vc_tip_tier_second {\
1605 .vc_tip_tier_third {\
1611 @keyframes vc_tooltip_show {\
1619 @keyframes vc_tooltip_click {\
1621 box-shadow: 0 0 8px 6px rgba(60, 220, 180, 0), 0 0 0px 0px rgba(255,255,255,1), 0 0 0px 0px rgba(60, 220, 180, 0);\
1624 box-shadow: 0 0 8px 6px rgba(60, 220, 180, 1), 0 0 3px 5px rgba(255,255,255,1), 0 0 6px 10px rgba(60, 220, 180, 1);\
1627 box-shadow: 0 0 8px 6px rgba(60, 220, 180, 0), 0 0 0px 20px rgba(255,255,255,0.5), 0 0 0px 20px rgba(60, 220, 180, 0);\
1630 @keyframes vc_click_effect {\
1632 transform: scale(0.3, 0.3);\
1636 transform: scale(0.5, 0.5);\
1640 transform: scale(1, 1);\
1644 vc_style_node.appendChild(vc_style_text);
1645 document.head.appendChild(vc_style_node);
1647 var frames = window.frames;
1649 for (var i = 0; i < frames.length; i++) {
1651 var doc = frames[i].document;
1652 var vc_frame_style_node = doc.createElement('style');
1653 var vc_frame_style_text = doc.createTextNode(vc_style_node.textContent);
1655 vc_frame_style_node.appendChild(vc_frame_style_text);
1656 doc.head.appendChild(vc_frame_style_node);
1662 /* remove indicators on scroll */
1663 window.addEventListener('scroll', function () { //[TODO] need to remove the listener