1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-09-18 08:52:26 +02:00

Further improve logger along with fixes to specific logger issues

- Add tooltip support (not yet complete):
  https://github.com/gorhill/uBlock/issues/1222
- Add a link to logger documentation:
  https://github.com/gorhill/uBlock/issues/2876#issuecomment-322905413
- Migrate to from fontawesome font to svgs:
  https://github.com/uBlockOrigin/uBlock-issues/issues/249
This commit is contained in:
Raymond Hill 2018-12-17 13:54:17 -05:00
parent 9d756147cf
commit 56f7505ad8
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
7 changed files with 236 additions and 106 deletions

View File

@ -543,12 +543,72 @@
"message":"Current tab",
"description":"Appears in the logger's tab selector"
},
"loggerReloadTip":{
"message":"Reload the tab content",
"description":"Tooltip for the reload button in the logger page"
},
"loggerDomInspectorTip":{
"message":"Toggle the DOM inspector",
"description":"Tooltip for the DOM inspector button in the logger page"
},
"loggerPopupPanelTip":{
"message":"Toggle the popup panel",
"description":"Tooltip for the popup panel button in the logger page"
},
"loggerInfoTip":{
"message":"uBlock Origin wiki: The logger",
"description":"Tooltip for the top-right info label in the logger page"
},
"loggerClearTip":{
"message":"Clear logger",
"description":"Tooltip for the eraser in the logger page"
},
"loggerPauseTip":{
"message":"Pause logger (discard all incoming data)",
"description":"Tooltip for the pause button in the logger page"
},
"loggerUnpauseTip":{
"message":"Unpause logger",
"description":"Tooltip for the play button in the logger page"
},
"loggerRowFiltererButtonTip":{
"message":"Toggle logger filtering",
"description":"Tooltip for the row filterer button in the logger page"
},
"logFilterPrompt":{
"message":"filter log entries",
"description": "English: filter log entries"
"message":"filter logger content",
"description": "Placeholder string for logger output filtering input field"
},
"loggerRowFiltererBuiltinTip":{
"message":"Logger filtering options",
"description":"Tooltip for the button to bring up logger output filtering options"
},
"loggerRowFiltererBuiltinNot":{
"message":"Not",
"description":"A keyword in the built-in row filtering expression"
},
"loggerRowFiltererBuiltinEventful":{
"message":"eventful",
"description":"A keyword in the built-in row filtering expression"
},
"loggerRowFiltererBuiltinBlocked":{
"message":"blocked",
"description":"A keyword in the built-in row filtering expression"
},
"loggerRowFiltererBuiltinAllowed":{
"message":"allowed",
"description":"A keyword in the built-in row filtering expression"
},
"loggerRowFiltererBuiltin1p":{
"message":"1st-party",
"description":"A keyword in the built-in row filtering expression"
},
"loggerRowFiltererBuiltin3p":{
"message":"3rd-party",
"description":"A keyword in the built-in row filtering expression"
},
"logMaxEntriesTip":{
"message":"Maximum number of log entries",
"message":"Maximum number of logger entries",
"description":"Tooltip informaing that the input field is to set the maximum number of entries in the log"
},
"loggerURLFilteringContextLabel":{

View File

@ -2,7 +2,7 @@
display: none;
}
#inspectors.dom #domInspector {
display: block;
display: flex;
}
#domInspector .permatoolbar .highlightMode.invert {
transform: rotate(180deg);

View File

@ -2,9 +2,13 @@ body {
background-color: white;
border: 0;
color: black;
display: flex;
flex-direction: column;
height: 100vh;
margin: 0;
overflow: hidden;
padding: 0;
width: 100vw;
}
input:focus {
background-color: #ffe;
@ -20,31 +24,18 @@ textarea {
border: 0;
box-sizing: border-box;
display: flex;
flex-shrink: 0;
font-size: 120%;
left: 0;
margin: 0;
padding: 0.25em 0.5em;
position: absolute;
top: 0;
z-index: 10;
}
.permatoolbar .button {
align-items: center;
background-color: white;
border: none;
box-sizing: border-box;
cursor: pointer;
display: inline-flex;
font-size: 150%;
margin: 0;
padding: 0.25em;
}
.permatoolbar .button.disabled {
opacity: 0.2;
pointer-events: none;
}
.permatoolbar .button.active {
font-weight: bold;
fill: #5F9EA0;
}
.permatoolbar .button:hover {
background-color: #eee;
@ -54,56 +45,72 @@ textarea {
padding: 0.25em 0;
width: 28em;
}
#showpopup {
display: inline-flex;
align-items: center;
}
#showpopup img {
filter: grayscale(100%);
height: auto;
width: 1em;
}
#info {
fill: #ccc;
padding-left: 0.5em;
padding-right: 0.5em;
position: absolute;
right: 0;
}
#info:hover {
fill: #000;
}
@media (max-width: 600px) {
#info {
display: none;
}
}
/*
https://github.com/gorhill/uBlock/issues/3293
=> https://devhints.io/css-system-font-stack
*/
#inspectors {
bottom: 0;
flex-grow: 1;
font-family: "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
font-size: 13px;
position: absolute;
top: 0;
width: 100%;
}
.inspector {
border-top: 1px solid #ccc;
bottom: 0;
position: absolute;
top: 0;
width: 100%;
display: flex;
flex-direction: column;
}
.vscrollable {
bottom: 0;
flex-grow: 1;
overflow-x: hidden;
overflow-y: auto;
position: absolute;
top: 0;
width: 100%;
}
.vCompactToggler.button:before {
content: '\f102';
.vCompact .vCompactToggler.button {
transform: scaleY(-1)
}
.vCompact .vCompactToggler.button:before {
content: '\f103';
}
.hCompactToggler.button:before {
content: '\f100';
}
.hCompact .hCompactToggler.button:before {
content: '\f101';
.hCompact .hCompactToggler.button {
transform: scaleX(-1)
}
#inspectors.dom #netInspector {
display: none;
}
#netInspector #pause > span:last-of-type {
display: none;
}
#netInspector.paused #pause > span:first-of-type {
display: none;
}
#netInspector.paused #pause > span:last-of-type {
display: inline-flex;
fill: #5F9EA0;
}
#netInspector #filterExprGroup {
display: flex;
margin: 0 1em;
@ -124,11 +131,11 @@ textarea {
#netInspector #maxEntries {
margin: 0 2em;
}
#netInspector #filterExprButton::before {
content: '\f078';
#netInspector #filterExprButton {
transform: scaleY(-1);
}
#netInspector #filterExprButton.expanded::before {
content: '\f077';
#netInspector #filterExprButton.expanded {
transform: scaleY(1);
}
#netInspector #filterExprPicker {
background-color: white;
@ -138,10 +145,12 @@ textarea {
flex-direction: column;
font-size: small;
margin-top: 0.2em;
right: 0;
top: 100%;
z-index: 100;
}
#netInspector #filterExprButton.on {
color: #5F9EA0;
fill: #5F9EA0;
}
#netInspector #filterExprButton.expanded ~ #filterExprPicker {
display: flex;
@ -509,8 +518,12 @@ body[dir="rtl"] #netFilteringDialog .dialog > div.headers > span.tools {
#netFilteringDialog .dialog > div.headers > span.tools > span {
cursor: pointer;
font-size: 1.2em;
padding: 0.2em 0.4em;
text-align: center;
}
#netFilteringDialog .dialog > div.headers > span.tools > span:hover {
background-color: #eee;
}
#netFilteringDialog .dialog > div.containers {
height: 40vh;
overflow: hidden;
@ -530,9 +543,9 @@ body[dir="rtl"] #netFilteringDialog .dialog > div.headers > span.tools {
background-color: #ffe;
border: 1px solid #ddc;
border-radius: 4px;
color: #888;
fill: #888;
cursor: pointer;
font-size: 1.8em;
font-size: 1.6em;
margin: 0.1em;
padding: 0.25em 0.5em;
visibility: hidden;
@ -541,7 +554,7 @@ body.dirty #netFilteringDialog .dialog > div.containers > div.dynamic > table.to
visibility: visible;
}
#netFilteringDialog .dialog > div.containers > div.dynamic > table.toolbar #saveRules:hover {
color: black;
fill: black;
}
#netFilteringDialog .dialog > div.containers > div.dynamic > table.toolbar tr.entry {
display: none;
@ -662,16 +675,21 @@ body.colorBlind #netFilteringDialog .dialog > div.containers > div.dynamic tr.e
#filterFinderDialog .dialog ul {
font-size: larger;
}
#filterFinderDialog .filterFinderListEntry {
align-items: center;
display: flex;
}
#filterFinderDialog .filterFinderListEntry a {
text-decoration: none;
}
#filterFinderDialog .filterFinderListEntry a.fa {
opacity: 0.8;
#filterFinderDialog .filterFinderListEntry a.fa-icon {
margin: 0 0.5em;
opacity: 0.6;
}
#filterFinderDialog .filterFinderListEntry a.fa:hover {
#filterFinderDialog .filterFinderListEntry a.fa-icon:hover {
opacity: 1;
}
#filterFinderDialog .filterFinderListEntry a[href=""]:nth-of-type(2) {
#filterFinderDialog .filterFinderListEntry a.fa-icon[href=""] {
display: none;
}
#filterFinderDialog .dialog > *:first-child {

View File

@ -617,14 +617,14 @@ var toggleHCompactView = function() {
};
/******************************************************************************/
/*
var toggleHighlightMode = function() {
messaging.sendTo(inspectorConnectionId, {
what: 'highlightMode',
invert: uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').classList.toggle('invert')
});
};
*/
/******************************************************************************/
var revert = function() {
@ -644,7 +644,7 @@ const toggleOn = function() {
domTree.addEventListener('mouseover', onMouseOver, true);
uDom.nodeFromSelector('#domInspector .vCompactToggler').addEventListener('click', toggleVCompactView);
uDom.nodeFromSelector('#domInspector .hCompactToggler').addEventListener('click', toggleHCompactView);
uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').addEventListener('click', toggleHighlightMode);
//uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').addEventListener('click', toggleHighlightMode);
uDom.nodeFromSelector('#domInspector .permatoolbar .revert').addEventListener('click', revert);
uDom.nodeFromSelector('#domInspector .permatoolbar .commit').addEventListener('click', startDialog);
injectInspector();
@ -662,7 +662,7 @@ const toggleOff = function() {
domTree.removeEventListener('mouseover', onMouseOver, true);
uDom.nodeFromSelector('#domInspector .vCompactToggler').removeEventListener('click', toggleVCompactView);
uDom.nodeFromSelector('#domInspector .hCompactToggler').removeEventListener('click', toggleHCompactView);
uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').removeEventListener('click', toggleHighlightMode);
//uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').removeEventListener('click', toggleHighlightMode);
uDom.nodeFromSelector('#domInspector .permatoolbar .revert').removeEventListener('click', revert);
uDom.nodeFromSelector('#domInspector .permatoolbar .commit').removeEventListener('click', startDialog);
inspectedTabId = 0;
@ -676,6 +676,7 @@ const toggle = function() {
} else {
toggleOff();
}
logger.resize();
};
/******************************************************************************/

View File

@ -32,7 +32,9 @@
const messaging = vAPI.messaging;
const logger = self.logger = { ownerId: Date.now() };
let popupLoggerBox;
let popupLoggerTooltips;
let activeTabId;
let netInspectorPaused = false;
/******************************************************************************/
@ -63,43 +65,29 @@ const tabIdFromPageSelector = logger.tabIdFromPageSelector = function() {
/******************************************************************************/
/******************************************************************************/
// Adjust top padding of content table, to match that of toolbar height.
const tbody = document.querySelector('#netInspector tbody');
const trJunkyard = [];
const tdJunkyard = [];
const firstVarDataCol = 1;
const lastVarDataIndex = 6;
const reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/;
const netFilteringDialog = uDom.nodeFromId('netFilteringDialog');
(function() {
var toolbar = uDom.nodeFromSelector('body > .permatoolbar');
var size = toolbar.clientHeight + 'px';
uDom('#inspectors').css('top', size);
uDom('.vscrollable').css('padding-top', size);
})();
/******************************************************************************/
var tbody = document.querySelector('#netInspector tbody');
var trJunkyard = [];
var tdJunkyard = [];
var firstVarDataCol = 1;
var lastVarDataIndex = 6;
var maxEntries = 5000;
var allTabIds = new Map();
var allTabIdsToken;
var reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/;
var netFilteringDialog = uDom.nodeFromId('netFilteringDialog');
var prettyRequestTypes = {
const prettyRequestTypes = {
'main_frame': 'doc',
'stylesheet': 'css',
'sub_frame': 'frame',
'xmlhttprequest': 'xhr'
};
var uglyRequestTypes = {
const uglyRequestTypes = {
'doc': 'main_frame',
'css': 'stylesheet',
'frame': 'sub_frame',
'xhr': 'xmlhttprequest'
};
var staticFilterTypes = {
const staticFilterTypes = {
'beacon': 'other',
'doc': 'document',
'css': 'stylesheet',
@ -109,6 +97,10 @@ var staticFilterTypes = {
'xhr': 'xmlhttprequest'
};
let maxEntries = 5000;
let allTabIds = new Map();
let allTabIdsToken;
/******************************************************************************/
var classNameFromTabId = function(tabId) {
@ -518,6 +510,17 @@ const onLogBufferRead = function(response) {
return;
}
// Disable tooltips?
if (
popupLoggerTooltips === undefined &&
response.tooltips !== undefined
) {
popupLoggerTooltips = response.tooltips;
if ( popupLoggerTooltips === false ) {
uDom('[data-i18n-title]').attr('title', '');
}
}
// Tab id of currently active tab
let activeTabIdChanged = false;
if ( response.activeTabId ) {
@ -546,7 +549,9 @@ const onLogBufferRead = function(response) {
pageSelectorFromURLHash();
}
renderLogEntries(response);
if ( netInspectorPaused === false ) {
renderLogEntries(response);
}
if ( rowVoided ) {
uDom('#clean').toggleClass(
@ -1639,18 +1644,26 @@ var cleanBuffer = function() {
/******************************************************************************/
var toggleVCompactView = function() {
const pauseNetInspector = function() {
netInspectorPaused = uDom.nodeFromId('netInspector')
.classList
.toggle('paused');
};
/******************************************************************************/
const toggleVCompactView = function() {
uDom.nodeFromId('netInspector').classList.toggle('vCompact');
uDom('#netInspector .vExpanded').toggleClass('vExpanded');
};
var toggleVCompactRow = function(ev) {
const toggleVCompactRow = function(ev) {
ev.target.parentElement.classList.toggle('vExpanded');
};
/******************************************************************************/
var popupManager = (function() {
const popupManager = (function() {
let realTabId = 0;
let popup = null;
let popupObserver = null;
@ -1737,6 +1750,37 @@ var popupManager = (function() {
/******************************************************************************/
logger.resize = (function() {
let timer;
const resize = function() {
const vrect = document.body.getBoundingClientRect();
const elems = document.querySelectorAll('.vscrollable');
for ( const elem of elems ) {
const crect = elem.getBoundingClientRect();
const dh = crect.bottom - vrect.bottom;
if ( dh === 0 ) { continue; }
elem.style.height = (crect.height - dh) + 'px';
}
};
const resizeAsync = function() {
if ( timer !== undefined ) { return; }
timer = self.requestAnimationFrame(( ) => {
timer = undefined;
resize();
});
};
resizeAsync();
window.addEventListener('resize', resizeAsync, { passive: true });
return resizeAsync;
})();
/******************************************************************************/
const grabView = function() {
if ( logger.ownerId === undefined ) {
logger.ownerId = Date.now();
@ -1769,6 +1813,7 @@ uDom('#showpopup').on('click', popupManager.toggleOn);
uDom('#netInspector .vCompactToggler').on('click', toggleVCompactView);
uDom('#clean').on('click', cleanBuffer);
uDom('#clear').on('click', clearBuffer);
uDom('#pause').on('click', pauseNetInspector);
uDom('#maxEntries').on('change', onMaxEntriesChanged);
uDom('#netInspector table').on('click', 'tr > td:nth-of-type(1)', toggleVCompactRow);
uDom('#netInspector').on('click', 'tr.canLookup > td:nth-of-type(2)', reverseLookupManager.toggleOn);

View File

@ -1135,7 +1135,8 @@ const getLoggerData = function(details, activeTabId, callback) {
entries: µb.logger.readAll(details.ownerId),
maxEntries: µb.userSettings.requestLogMaxEntries,
activeTabId: activeTabId,
tabIdsToken: µb.pageStoresToken
tabIdsToken: µb.pageStoresToken,
tooltips: µb.userSettings.tooltipsDisabled === false
};
if ( µb.pageStoresToken !== details.tabIdsToken ) {
const tabIds = new Map();

View File

@ -2,8 +2,10 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Does not work well:
<meta name="viewport" content="width=device-width, initial-scale=1"> -->
<link rel="stylesheet" type="text/css" href="css/common.css">
<link rel="stylesheet" href="css/fa-icons.css" type="text/css">
<link rel="stylesheet" type="text/css" href="css/logger-ui.css">
<link rel="stylesheet" type="text/css" href="css/logger-ui-inspector.css">
<link rel="shortcut icon" type="image/png" href="img/icon_16.png">
@ -17,20 +19,21 @@
<option value="tab_bts" data-i18n="logBehindTheScene">
<option value="tab_active" data-i18n="loggerCurrentTab">
</select>
<span id="refresh" class="button fa disabled needdom">&#xf021;</span>
<span id="showdom" class="button fa disabled needdom">&#xf121;</span>
<span id="showpopup" class="button disabled needdom"><img src="/img/icon_64.png"></span>
<span id="refresh" class="button fa-icon disabled needdom" data-i18n-title="loggerReloadTip">sync-alt</span>
<span id="showdom" class="button fa-icon disabled needdom" data-i18n-title="loggerDomInspectorTip">code</span>
<span id="showpopup" class="button fa-icon disabled needdom" data-i18n-title="loggerPopupPanelTip"><img src="/img/icon_64.png"></span>
<a id="info" class="button fa-icon" href="https://github.com/gorhill/uBlock/wiki/The-logger" target="_blank" data-i18n-title="loggerInfoTip">info-circle</a>
</div>
<div id="inspectors">
<div id="domInspector" class="inspector vCompact hCompact">
<div class="permatoolbar">
<div>
<span class="button fa vCompactToggler"></span>
<span class="button fa hCompactToggler"></span>
<span class="button fa highlightMode" style="display: none">&#xf042;</span>
<span class="button fa revert disabled">&#xf12d;</span>
<span class="button fa commit disabled">&#xf0c7;</span>
<span class="button fa-icon vCompactToggler">angle-double-up</span>
<span class="button fa-icon hCompactToggler">angle-double-left</span>
<!-- <span class="button fa highlightMode" style="display: none">&#xf042;</span> -->
<span class="button fa-icon revert disabled">eraser</span>
<span class="button fa-icon commit disabled">save</span>
</div>
</div>
<div class="vscrollable">
@ -39,17 +42,18 @@
</div>
<div id="netInspector" class="inspector vCompact f">
<div class="permatoolbar">
<span class="button fa vCompactToggler"></span>
<span id="clean" class="button fa disabled">&#xf00d;</span>
<span id="clear" class="button fa disabled">&#xf12d;</span>
<span class="button fa-icon vCompactToggler">angle-double-up</span>
<span id="clean" class="button fa-icon disabled">times</span>
<span id="clear" class="button fa-icon disabled" data-i18n-title="loggerClearTip">eraser</span>
<span id="pause"><span class="button fa-icon" data-i18n-title="loggerPauseTip">pause-circle</span><span class="button fa-icon" data-i18n-title="loggerUnpauseTip">play-circle</span></span>
<span id="filterExprGroup">
<span id="filterButton" class="button fa">&#xf0b0;</span>
<span id="filterButton" class="button fa-icon" data-i18n-title="loggerRowFiltererButtonTip">filter</span>
<input id="filterInput" type="text" placeholder="logFilterPrompt">
<span id="filterExprButton" class="button fa"></span>
<span id="filterExprButton" class="button fa-icon" data-i18n-title="loggerRowFiltererBuiltinTip">angle-up</span>
<div id="filterExprPicker">
<div><span data-filtex="!">Not</span><span data-filtex="^--$|^\+\+$|^\*\*$|^<<$|^##|^#@#">eventful</span><span data-filtex="^--$|^<<$|^##">blocked</span><span data-filtex="^\+\+$|^#@#">allowed</span></div>
<div><span data-filtex="!">Not</span><span data-filtex="^(?:css|font)$">css/font</span><span data-filtex="^image$">image</span><span data-filtex="^(?:inline-)?script(ing)?$">script</span><span data-filtex="^(?:websocket|xhr)$">xhr</span><span data-filtex="^frame$">frame</span><span data-filtex="^dom$">dom</span></div>
<div><span data-filtex="!">Not</span><span data-filtex="^1$">1st-party</span><span data-filtex="^3(?:,\d)?$">3rd-party</span></div>
<div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="^--$|^\+\+$|^\*\*$|^<<$|^##|^#@#" data-i18n="loggerRowFiltererBuiltinEventful"></span><span data-filtex="^--$|^<<$|^##" data-i18n="loggerRowFiltererBuiltinBlocked"></span><span data-filtex="^\+\+$|^#@#" data-i18n="loggerRowFiltererBuiltinAllowed"></span></div>
<div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="^(?:css|font)$">css/font</span><span data-filtex="^image$">image</span><span data-filtex="^(?:inline-)?script(?:ing)?$">script</span><span data-filtex="^(?:websocket|xhr)$">xhr</span><span data-filtex="^frame$">frame</span><span data-filtex="^dom$">dom</span></div>
<div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="^1$" data-i18n="loggerRowFiltererBuiltin1p"></span><span data-filtex="^3(?:,\d)?$" data-i18n="loggerRowFiltererBuiltin3p"></span></div>
</div>
</span>
<input id="maxEntries" type="text" size="5" data-i18n-title="logMaxEntriesTip">
@ -73,7 +77,7 @@
<div class="headers">
<span class="header dynamic selected" data-container="dynamic" data-i18n="loggerURLFilteringHeader"></span>
<span class="header static" data-container="static" data-i18n="loggerStaticFilteringHeader"></span>
<span class="tools"><span class="fa reload">&#xf021;</span>&emsp;<span class="fa picker">&#xf1fb;</span></span>
<span class="tools"><span class="fa-icon reload">sync-alt</span>&ensp;<span class="fa-icon picker">eye-dropper</span></span>
</div>
<div class="containers">
<div class="container dynamic selected">
@ -81,7 +85,7 @@
<colgroup><col><col></colgroup>
<tbody>
<tr>
<td><span id="saveRules" class="fa">&#xf023;</span>
<td><span id="saveRules" class="fa-icon">lock</span>
<td><p>
<label><span data-i18n="loggerURLFilteringContextLabel"></span> <select class="dynamic origin"></select></label>&emsp;
<label><span data-i18n="loggerURLFilteringTypeLabel"></span> <select class="dynamic type"><option><option value="*">*</select></label>
@ -111,7 +115,7 @@
<ul id="filterFinderListEntry">
<li class="filterFinderListEntry">
<a href="asset-viewer.html?url=" target="_blank"></a>
<a href="" class="fa" target="_blank">&#xf015;</span></li>
<a href="" class="fa-icon" target="_blank">home</a></li>
</ul>
<div id="cosmeticFilteringDialog" class="modalDialog">
<div class="dialog">
@ -121,6 +125,7 @@
</div>
</div>
<script src="js/fa-icons.js"></script>
<script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>