Popup: aspect ratio buttons are working. Fixed some bugs with crop calculation.

This commit is contained in:
Tamius Han 2018-05-27 01:29:02 +02:00
parent ba26db28ea
commit 293b784704
8 changed files with 197 additions and 225 deletions

View File

@ -108,7 +108,7 @@ var ExtensionConf = {
colors:{
// criticalFail: "background: #fa2; color: #000"
},
keybinds: {
keyboard: {
shortcuts: {
// automatic
"a": {

View File

@ -32,7 +32,7 @@ class Keybinds {
// building modifiers list:
var modlist = "";
for(var mod of ExtensionConf.keybinds.modKeys){
for(var mod of ExtensionConf.keyboard.modKeys){
if(event[mod])
modlist += (mod + "_")
}
@ -48,8 +48,8 @@ class Keybinds {
console.log("[Keybinds::_kbd_process] our full keypress is this", keypress );
if(ExtensionConf.keybinds.shortcuts[keypress]){
var conf = ExtensionConf.keybinds.shortcuts[keypress];
if(ExtensionConf.keyboard.shortcuts[keypress]){
var conf = ExtensionConf.keyboard.shortcuts[keypress];
if(Debug.debug && Debug.keyboard)
console.log("[Keybinds::_kbd_process] there's an action associated with this keypress. conf:", conf);

View File

@ -1,3 +1,7 @@
if(Debug.debug){
console.log("Loading Comms.js")
}
class CommsClient {
constructor(name){
this.port = browser.runtime.connect({name: name});
@ -17,7 +21,7 @@ class CommsClient {
}
if(message.cmd === "set-ar"){
this.pageInfo.setAr(message.ar);
this.pageInfo.setAr(message.ratio);
} else if (message.cmd === "has-videos") {
} else if (message.cmd === "set-config") {
@ -85,11 +89,11 @@ class CommsClient {
async requestSettings(){
if(Debug.debug){
console.log("%c[CommsClient::requestSettings] sending request for congif!", "background: #11D; color: #DDA");
console.log("%c[CommsClient::requestSettings] sending request for congif!", "background: #11D; color: #aad");
}
var response = await this.sendMessage_nonpersistent({cmd: 'get-config'});
if(Debug.debug){
console.log("%c[CommsClient::requestSettings] received settings response!", "background: #11D; color: #DDA", response);
console.log("%c[CommsClient::requestSettings] received settings response!", "background: #11D; color: #aad", response);
}
if(! response || response.extensionConf){
@ -139,6 +143,10 @@ class CommsServer {
}
sendToActive(message) {
if(Debug.debug && Debug.comms){
console.log("%c[CommsServer::sendToActive] trying to send a message to active tab. Message:", "background: #dda; color: #11D", message);
}
if(BrowserDetect.firefox){
this._sendToActive_ff(message);
} else if (BrowserDetect.chrome) {
@ -147,8 +155,17 @@ class CommsServer {
}
async _sendToActive_ff(message){
var activeTab = await browser.tabs.query({currentWindow: true, active: true});
for (key in this.ports[tabs[0].id]) {
var tabs = await browser.tabs.query({currentWindow: true, active: true});
if(Debug.debug && Debug.comms){
console.log("[CommsServer::_sendToActive_ff] currently active tab(s)?", tabs);
for (var key in this.ports[tabs[0].id]) {
console.log("key?", key, this.ports[tabs[0].id]);
// this.ports[tabs[0].id][key].postMessage(message);
}
}
for (var key in this.ports[tabs[0].id]) {
this.ports[tabs[0].id][key].postMessage(message);
}
}
@ -166,10 +183,17 @@ class CommsServer {
}
onConnect(port){
console.log("on connect!", port.sender.tab.id, port)
var ths = this;
// poseben primer | special case
if (port.name === 'popup-port') {
this.popupPort = port;
this.popupPort.onMessage.addListener( (m,p) => ths.processReceivedMessage(m,p));
return;
}
var tabId = port.sender.tab.id;
var frameId = port.sender.frameId;
var ths = this;
if(! this.ports[tabId]){
this.ports[tabId] = {};
}
@ -191,17 +215,23 @@ class CommsServer {
if (message.cmd === 'get-config') {
port.postMessage({cmd: "set-config", conf: ExtensionConf})
}
if (message.cmd === 'set-ar') {
this.sendToActive(message);
}
if (message.cmd === 'autoar-enable') {
this.sendToActive(message);
}
}
processReceivedMessage_nonpersistent_ff(message, sender){
if (Debug.debug && Debug.comms) {
console.log("%c[CommsServer.js::processMessage_nonpersistent_ff] Received message from background script!", "background-color: #11D; color: #DDA", message, sender);
console.log("%c[CommsServer.js::processMessage_nonpersistent_ff] Received message from background script!", "background-color: #11D; color: #aad", message, sender);
}
if (message.cmd === 'get-config') {
var ret = {extensionConf: JSON.stringify(ExtensionConf)};
if (Debug.debug && Debug.comms) {
console.log("%c[CommsServer.js::processMessage_nonpersistent_ff] Returning this:", "background-color: #11D; color: #DDA", ret);
console.log("%c[CommsServer.js::processMessage_nonpersistent_ff] Returning this:", "background-color: #11D; color: #aad", ret);
}
Promise.resolve(ret);
}
@ -222,11 +252,35 @@ class CommsServer {
}
}
var _com_chrome_tabquery_wrapper = async function(tabInfo){
class Comms {
static async sendMessage(message){
if(BrowserDetect.firefox){
return browser.runtime.sendMessage(message)
} else {
return new Promise((resolve, reject) => {
try{
if(BrowserDetect.edge){
browser.runtime.sendMessage(message, function(response){
var r = response;
resolve(r);
});
} else {
chrome.runtime.sendMessage(message, function(response){
// Chrome/js shittiness mitigation — remove this line and an empty array will be returned
var r = response;
resolve(r);
});
}
}
catch(e){
reject(e);
}
});
}
}
}
var _com_queryTabs = async function(tabInfo){
if(BrowserDetect.usebrowser != "firefox"){
return await _com_chrome_tabquery_wrapper(tabInfo);

View File

@ -196,6 +196,9 @@ class PageInfo {
}
setAr(ar){
if(ar !== 'auto') {
this.stopArDetection();
}
// TODO: find a way to only change aspect ratio for one video
for(var vd of this.videos){
vd.setAr(ar)

View File

@ -69,7 +69,14 @@ class Scaler {
}
// if 'ar' is string, we'll handle that in legacy wrapper
// če je 'ar' string, potem bomo z njim opravili v legacy wrapperju. Seveda obstaja izjema
// if 'ar' is string, we'll handle that in legacy wrapper, with one exception
if(mode === 'reset'){
return {xFactor: 1, yFactor: 1}
}
var ar = 0;
if(isNaN(mode)){
ar = this.modeToAr(mode);
@ -128,7 +135,7 @@ class Scaler {
videoDimensions.yFactor = videoDimensions.xFactor;
}
else {
videoDimensions.xFactor = fileAr / Math.max(ar, player);
videoDimensions.xFactor = fileAr / Math.min(ar, playerAr);
videoDimensions.yFactor = videoDimensions.xFactor;
}

View File

@ -91,7 +91,7 @@ async function init(){
// config.arConf.enabled_global = ExtensionConf.arDetect.enabled == "global";
// var keybinds = ExtensionConf.keybinds.shortcuts;
// var keybinds = ExtensionConf.keyboard.shortcuts;
// if(Debug.debug)
// console.log("[uw-bg::_uwbg_rcvmsg] Keybinds.fetch returned this:", keybinds);

View File

@ -38,98 +38,20 @@ var hasVideos = false;
var _config;
var _changeAr_button_shortcuts = { "autoar":"none", "reset":"none", "219":"none", "189":"none", "169":"none" }
var comms = new Comms();
var port = browser.runtime.connect({name: 'popup-port'});
port.onMessage.addListener( (m,p) => processReceivedMessage(m,p));
// async function test(){
// var message = {cmd: "testing"};
// try{
// var tabs = await Comms.queryTabs({currentWindow: true, active: true});
// if(Debug.debug)
// console.log("[popup.js::test] trying to send message", message, " to tab ", tabs[0], ". (all tabs:", tabs,")");
//
// var response = await browser.tabs.sendMessage(tabs[0].id, message);
// console.log("[popup.js::test] response is this:",response);
// }
// catch(e){
// console.log("[popup.js::test] sending message failed. prolly cos browser.tabs no worky?", e);
// }
// }
// test();
async function sendMessage(message){
console.log("SENDING MESSAGE TO CONTENT SCRIPT");
var tabs = await browser.tabs.query({currentWindow: true, active: true});
if(Debug.debug)
console.log("[uw-bg::sendMessage] trying to send message", message, " to tab ", tabs[0], ". (all tabs:", tabs,")");
var response = await browser.tabs.sendMessage(tabs[0].id, message);
console.log("[uw-bg::sendMessage] response is this:",response);
return response;
async function processReceivedMessage(message, port){
if(message.cmd === 'set-config'){
this.loadConfig(message.conf);
}
}
function hideWarning(warn){
document.getElementById(warn).classList.add("hidden");
}
function check4videos(){
Comms.sendToBackgroundScript({cmd: "has-videos"})
.then(response => {
if(Debug.debug){
console.log("[popup.js::check4videos] received response:",response, "has video?", response.response.hasVideos);
}
if(response.response.hasVideos){
hasVideos = true;
// openMenu(selectedMenu);
hideWarning("no-videos-warning");
}
else{
// brute force error mitigation.
setTimeout(check4videos, 2000);
}
})
.catch(error => {
if(Debug.debug)
console.log("%c[popup.js::check4videos] sending message failed with error", "color: #f00", error, "%c retrying in 1s ...", "color: #f00");
setTimeout(check4videos, 1000);
});
}
function check4conf(){
Comms.sendToBackgroundScript({cmd: "get-config"})
.then(response => {
if(Debug.debug)
console.log("[popup.js::check4conf] received response to get-config request:",response, response.response);
loadConfig(response.response);
})
.catch(error => {
if(Debug.debug)
console.log("%c[popup.js::check4conf] sending message failed with error", "color: #f00", error, "%c retrying in 1s ...", "color: #f00");
setTimeout(check4conf, 1000);
});
}
function check4siteStatus(){
Comms.sendToBackgroundScript({cmd: "uw-enabled-for-site"})
.then(response => {
if(Debug.debug)
console.log("[popup::check4siteStatus] received response:", response);
document.extensionEnabledOnCurrentSite.mode.value = response.response;
})
.catch(error => {
if(Debug.debug)
console.log("%c[popup.js::check4siteStatus] sending message failed with error", "color: #f00", error, "%c retrying in 1s ...", "color: #f00");
// setTimeout(check4siteStatus, 1000);
});
}
function stringToKeyCombo(key_in){
var keys_in = key_in.split("_");
var keys_out = "";
@ -148,66 +70,55 @@ function stringToKeyCombo(key_in){
return keys_out;
}
function loadConfig(config){
function loadConfig(extensionConf){
if(Debug.debug)
console.log("[popup.js::loadConfig] loading config. conf object:",config);
console.log("[popup.js::loadConfig] loading config. conf object:", extensionConf);
_config = config;
_extensionConf = extensionConf;
console.log(".... site status, config mode:", config.site.status, config.mode);
document.getElementById("current-site-status-global-status").innerHTML = config.mode == "blacklist" ? "<span style='color: #1f4'>allow</span>" : "<span style='color: #f00'>deny</span>";
if(config.site.status == "blacklisted" || (config.site.status == "follow-global" && config.mode == "whitelist") ){
openMenu("thisSite");
// document.getElementById("current-site-blacklisted").classList.remove("hidden");
// if(config.mode == "whitelist"){
// document.getElementById("current-site-whitelist-only").classList.remove("hidden");
// }
document.getElementById("extensionEnabledCurrentSite_blacklisted").setAttribute("checked","checked");
}
else if(config.site.status == "whitelisted"){
document.getElementById("extensionEnabledCurrentSite_whitelisted").setAttribute("checked","checked");
}
else {
document.getElementById("extensionEnabledCurrentSite_followGlobal").setAttribute("checked","checked");
}
document.getElementById("_checkbox_autoArEnabled").checked = config.arMode == "blacklist";
document.getElementById("_autoAr_disabled_reason").textContent = config.arDisabledReason;
document.getElementById("_input_autoAr_frequency").value = parseInt(1000/config.arTimerPlaying);
document.getElementById("_checkbox_autoArEnabled").checked = extensionConf.arDetect.mode == "blacklist";
document.getElementById("_autoAr_disabled_reason").textContent = extensionConf.arDetect.DisabledReason;
document.getElementById("_input_autoAr_timer").value = extensionConf.arDetect.timer_playing;
// process video alignment:
if(config.videoAlignment){
if(extensionConf.miscFullscreenSettings.videoFloat){
for(var button in ArPanel.alignment)
ArPanel.alignment[button].classList.remove("selected");
ArPanel.alignment[config.videoAlignment].classList.add("selected");
ArPanel.alignment[extensionConf.miscFullscreenSettings.videoFloat].classList.add("selected");
}
// process keyboard shortcuts:
if(config.keyboardShortcuts){
for(var key in config.keyboardShortcuts){
var shortcut = config.keyboardShortcuts[key];
if(extensionConf.keyboard.shortcuts){
for(var key in extensionConf.keyboard.shortcuts){
var shortcut = extensionConf.keyboard.shortcuts[key];
var keypress = stringToKeyCombo(key);
try{
if(shortcut.action == "char"){
if(shortcut.targetAr == 2.0){
if(shortcut.action == "crop"){
if(shortcut.arg == 2.0){
_changeAr_button_shortcuts["189"] = keypress;
}
else if(shortcut.targetAr == 2.39){
else if(shortcut.arg == 2.39){
_changeAr_button_shortcuts["219"] = keypress;
}
else if(shortcut.targetAr == 1.78){
else if(shortcut.arg == 1.78){
_changeAr_button_shortcuts["169"] = keypress;
}
else if(shortcut.arg == "fitw") {
_changeAr_button_shortcuts["fitw"] = keypress;
}
else if(shortcut.arg == "fith") {
_changeAr_button_shortcuts["fith"] = keypress;
}
else if(shortcut.arg == "reset") {
_changeAr_button_shortcuts["reset"] = keypress;
}
}
else{
_changeAr_button_shortcuts[shortcut.action] = keypress;
else if(shortcut.action == "auto-ar") {
_changeAr_button_shortcuts["auto-ar"] = keypress;
}
}
catch(Ex){
@ -216,7 +127,7 @@ function loadConfig(config){
}
for(var key in _changeAr_button_shortcuts){
try{
document.getElementById("_b_changeAr_" + key + "_key").textContent = "(" + _changeAr_button_shortcuts[key] + ")";
document.getElementById(`_b_changeAr_${key}_key`).textContent = `(${_changeAr_button_shortcuts[key]})`;
}
catch(ex){
@ -227,8 +138,14 @@ function loadConfig(config){
// process aspect ratio settings
showArctlButtons();
if(Debug.debug)
console.log("[popup.js::loadConfig] config loaded");
}
async function getConf(){
port.postMessage({cmd: 'get-config'});
}
function openMenu(menu){
if(Debug.debug){
@ -244,14 +161,14 @@ function openMenu(menu){
}
if(menu == "arSettings" || menu == "cssHacks" ){
// if(!hasVideos)
// Menu.noVideo.classList.remove("hidden");
// else{
// if(!hasVideos)
// Menu.noVideo.classList.remove("hidden");
// else{
Menu[menu].classList.remove("hidden");
if(Debug.debug){
console.log("[popup.js::openMenu] unhid", menu, "| element: ", Menu[menu]);
// }
}
}
// }
}
else{
Menu[menu].classList.remove("hidden");
@ -284,30 +201,30 @@ function showArctlButtons(){
if(! _config)
return;
// if(_config.arConf){
// if(! _config.arConf.enabled_global){
// ArPanel.autoar.disable.classList.add("hidden");
// ArPanel.autoar.enable.classList.remove("hidden");
//
// ArPanel.autoar.enable_tmp.textContent = "Temporarily enable";
// ArPanel.autoar.disable_tmp.textContent = "Temporarily disable";
// }
// else{
// ArPanel.autoar.disable.classList.remove("hidden");
// ArPanel.autoar.enable.classList.add("hidden");
//
// ArPanel.autoar.enable_tmp.textContent = "Re-enable";
// ArPanel.autoar.disable_tmp.textContent = "Temporarily disable";
// }
// if(! _config.arConf.enabled_current){
// ArPanel.autoar.disable_tmp.classList.add("hidden");
// ArPanel.autoar.enable_tmp.classList.remove("hidden");
// }
// else{
// ArPanel.autoar.disable_tmp.classList.remove("hidden");
// ArPanel.autoar.enable_tmp.classList.add("hidden");
// }
// }
// if(_config.arConf){
// if(! _config.arConf.enabled_global){
// ArPanel.autoar.disable.classList.add("hidden");
// ArPanel.autoar.enable.classList.remove("hidden");
// ArPanel.autoar.enable_tmp.textContent = "Temporarily enable";
// ArPanel.autoar.disable_tmp.textContent = "Temporarily disable";
// }
// else{
// ArPanel.autoar.disable.classList.remove("hidden");
// ArPanel.autoar.enable.classList.add("hidden");
// ArPanel.autoar.enable_tmp.textContent = "Re-enable";
// ArPanel.autoar.disable_tmp.textContent = "Temporarily disable";
// }
// if(! _config.arConf.enabled_current){
// ArPanel.autoar.disable_tmp.classList.add("hidden");
// ArPanel.autoar.enable_tmp.classList.remove("hidden");
// }
// else{
// ArPanel.autoar.disable_tmp.classList.remove("hidden");
// ArPanel.autoar.enable_tmp.classList.add("hidden");
// }
// }
}
@ -356,71 +273,63 @@ document.addEventListener("click", (e) => {
if(e.target.classList.contains("_changeAr")){
if(e.target.classList.contains("_ar_auto")){
command.cmd = "force-ar";
command.newAr = "auto";
command.arType = "legacy";
command.cmd = "autoar-enable";
command.enable = true;
return command;
}
if(e.target.classList.contains("_ar_reset")){
command.cmd = "force-ar";
command.newAr = "reset";
command.arType = "legacy";
command.cmd = "set-ar";
command.ratio = "reset";
return command;
}
if(e.target.classList.contains("_ar_fitw")){
command.cmd = "force-ar";
command.newAr = "fitw";
command.arType = "legacy";
command.cmd = "set-ar";
command.ratio = "fitw";
return command;
}
if(e.target.classList.contains("_ar_fitw")){
command.cmd = "force-ar";
command.newAr = "fith";
command.arType = "legacy";
command.cmd = "set-ar";
command.ratio = "fith";
return command;
}
if(e.target.classList.contains("_ar_219")){
command.cmd = "force-ar";
command.newAr = 2.39;
command.arType = "static";
command.cmd = "set-ar";
command.ratio = 2.39;
return command;
}
if(e.target.classList.contains("_ar_189")){
command.cmd = "force-ar";
command.newAr = 2.0;
command.arType = "static";
command.cmd = "set-ar";
command.ratio = 2.0;
return command;
}
if(e.target.classList.contains("_ar_169")){
command.cmd = "force-ar";
command.newAr = 1.78;
command.arType = "static";
command.cmd = "set-ar";
command.ratio = 1.78;
return command;
}
if(e.target.classList.contains("_ar_1610")){
command.cmd = "force-ar";
command.newAr = 1.6;
command.arType = "static";
command.cmd = "set-ar";
command.ratio = 1.6;
return command;
}
}
if(e.target.classList.contains("_autoAr")){
// var command = {};
// if(e.target.classList.contains("_autoar_temp-disable")){
// command = {cmd: "stop-autoar", sender: "popup", receiver: "uwbg"};
// }
// else if(e.target.classList.contains("_autoar_disable")){
// command = {cmd: "disable-autoar", sender: "popup", receiver: "uwbg"};
// }
// else if(e.target.classList.contains("_autoar_enable")){
// command = {cmd: "enable-autoar", sender: "popup", receiver: "uwbg"};
// }
// else{
// command = {cmd: "force-ar", newAr: "auto", sender: "popup", receiver: "uwbg"};
// }
// _arctl_onclick(command);
// return command;
// var command = {};
// if(e.target.classList.contains("_autoar_temp-disable")){
// command = {cmd: "stop-autoar", sender: "popup", receiver: "uwbg"};
// }
// else if(e.target.classList.contains("_autoar_disable")){
// command = {cmd: "disable-autoar", sender: "popup", receiver: "uwbg"};
// }
// else if(e.target.classList.contains("_autoar_enable")){
// command = {cmd: "enable-autoar", sender: "popup", receiver: "uwbg"};
// }
// else{
// command = {cmd: "force-ar", newAr: "auto", sender: "popup", receiver: "uwbg"};
// }
// _arctl_onclick(command);
// return command;
console.log("......");
var command = {};
if(e.target.classList.contains("_autoAr_enabled")){
@ -483,13 +392,14 @@ document.addEventListener("click", (e) => {
var command = getcmd(e);
if(command)
Comms.sendToAll(command);
port.postMessage(command);
return true;
});
hideWarning("script-not-running-warning");
openMenu(selectedMenu);
check4videos();
check4conf();
check4siteStatus();
// check4videos();
getConf();
// check4siteStatus();

View File

@ -222,7 +222,7 @@
<div id="autoar-basic-settings" class="suboption hidden">
<p><input type="checkbox" id="_checkbox_autoArEnabled" class="_autoAr_enabled _autoAr"> Enable automatic aspect ratio detection?<br/><small class="color_warn" id="_autoAr_disabled_reason"></small></p>
<p>Checks per second: <input id="_input_autoAr_frequency" class="_autoAr_frequency _autoAr" type="number" min="1" max="999"><span class="button _save_autoAr_frequency _autoAr">Save</span></p>
<p>Check every <input id="_input_autoAr_timer" class="_autoAr_timer _autoAr" type="number" min="5" max="10000"> ms — <span class="button _save_autoAr_frequency _autoAr">Save</span></p>
<!-- <div class="warning"><p>NOTE: increasing the frequency can result in <i>very</i> high RAM usage and slow performance.</p></div> -->
<p>
@ -242,17 +242,18 @@
<div class="row">
<span class="label">Cropping mode</span>
<div class="button-row">
<a class="button _changeAr _ar_auto w24">Auto-detect<br/><span id="_b_changeAr_autoar_key" class="smallcaps small darker"></span></a>
<a class="button _changeAr _ar_auto w24">Auto-detect<br/><span id="_b_changeAr_auto-ar_key" class="smallcaps small darker"></span></a>
<a class="button _changeAr _ar_reset w24">Reset<br/><span id="_b_changeAr_reset_key" class="smallcaps small darker"></span></a>
<a class="button _changeAr _ar_219 w24">21:9<br/><span id="_b_changeAr_219_key" class="smallcaps small darker"></span></a>
<a class="button _changeAr _ar_189 w24">2:1 (18:9)<br/><span id="_b_changeAr_189_key" class="smallcaps small darker"></span></a>
<a class="button _changeAr _ar_169 w24">16:9<br/><span id="_b_changeAr_169_key" class="smallcaps small darker"></span></a>
</div>
<span class="label">Stretching mode (todo)<br><small>Stretching is independent of crop mode. Crop gets applied before stretching.</small></span>
<span class="label">Stretching mode<br><small>Stretching is independent of crop mode. Crop gets applied before stretching.</small></span>
<div class="button-row">
<a class="button _changeAr _ar_reset w24">Reset<br/><span id="_b_changeAr_reset_key" class="smallcaps small darker"></span></a>
<a class="button _changeAr _ar_sw w24">Fill width<br/><span id="_b_changeAr_sw_key" class="smallcaps small darker"></span></a>
<a class="button _changeAr _ar_sh w24">Fill height<br/><span id="_b_changeAr_sh_key" class="smallcaps small darker"></span></a>
<a class="button _changeAr _ar_stretch_none w24">Never<br/><span id="_b_changeAr_reset_key" class="smallcaps small darker"></span></a>
<a class="button _changeAr _ar_stretch_basic w24">Basic<br/><span id="_b_changeAr_reset_key" class="smallcaps small darker"></span></a>
<a class="button _changeAr _ar_stretch_hybrid w24">Hybrid<br/><span id="_b_changeAr_sw_key" class="smallcaps small darker"></span></a>
<a class="button _changeAr _ar_stretch_conditional w24">Conditional<br/><span id="_b_changeAr_sh_key" class="smallcaps small darker"></span></a>
</div>
</div>
<!--<div class="row">
@ -297,9 +298,6 @@
<!-- load all scripts. ordering is important! -->
<script src="../../js/dep/jquery-3.1.1.js" ></script>
<script src="../../js/dep/chrome/chrome-extension-async.js" ></script>
<script src="../../js/conf/Debug.js"></script>
<script src="../../js/lib/BrowserDetect.js"></script>