index.html 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="apple-mobile-web-app-capable" content="yes" />
  6. <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
  7. <meta name="theme-color" content="#4b75ff">
  8. <link rel="stylesheet" href="../script/semantic/semantic.min.css">
  9. <script src="../script/jquery.min.js"></script>
  10. <script src="../script/ao_module.js"></script>
  11. <script src="../script/semantic/semantic.min.js"></script>
  12. <link rel="icon" type="image/png" href="img/module_icon.png">
  13. <link rel="manifest" crossorigin="use-credentials" href="manifest.json">
  14. <title>Browser</title>
  15. <style>
  16. body{
  17. overflow: hidden;
  18. }
  19. #urlbar{
  20. padding-top: 0.2em;
  21. padding-bottom: 0.2em;
  22. padding-left: 1.2em;
  23. padding-right: 0.3em;
  24. line-height: 1em;
  25. }
  26. .menuitem{
  27. margin-top: 0.2em;
  28. padding:0.2em !important;
  29. border: 0px !important;
  30. }
  31. .menuitem button{
  32. background-color: white !important;
  33. }
  34. .rightMenuItem{
  35. margin-top: 0.3em;
  36. padding:0.1em !important;
  37. border: 0px !important;
  38. }
  39. .rightMenuItem button:not(.inverted):not(.sidebarToggle.selected){
  40. background-color: white !important;
  41. }
  42. #starBtn{
  43. padding-right: 0.9em;
  44. cursor: pointer !important;
  45. background-color: white !important;
  46. }
  47. #starBtn:hover{
  48. opacity: 0.7;
  49. }
  50. #xframe{
  51. width: 100%;
  52. height: calc(100% - 43px);
  53. border: 0px solid transparent;
  54. }
  55. #notvdiWarning{
  56. position: fixed;
  57. bottom: 0px;
  58. left: 0px;
  59. width: 100%;
  60. padding: 0.4em;
  61. display: none;
  62. }
  63. #toolbar.proxy{
  64. border-bottom: 2px solid #41e8e5;
  65. }
  66. .sidebar{
  67. position: fixed;
  68. right: 0px;
  69. top: 42px;
  70. background-color: white;
  71. height: calc(100% - 42px);
  72. width: 25em;
  73. padding: 1.2em;
  74. border: 1px solid #dedede;
  75. box-shadow: -10px 1px 11px -5px rgba(0,0,0,0.15);
  76. -webkit-box-shadow: -10px 1px 11px -5px rgba(0,0,0,0.15);
  77. -moz-box-shadow: -10px 1px 11px -5px rgba(0,0,0,0.15);
  78. }
  79. .sidebarToggle.selected{
  80. background-color: #e2e2e2 !important;
  81. }
  82. #downloadDropper{
  83. border: 1px solid grey;
  84. width: 100%;
  85. height: 200px;
  86. cursor: move; /* fallback if grab cursor is unsupported */
  87. cursor: grab;
  88. padding: 1em;
  89. text-align: center;
  90. }
  91. </style>
  92. </head>
  93. <body>
  94. <div id="toolbar" class="ui top small attached menu" style="background-color: #eceef2; padding-left: 12px; padding-right: 12px;">
  95. <div class="ui menuitem">
  96. <button class="ui circular tiny icon button" onclick="undoPage();">
  97. <i class="arrow left icon"></i>
  98. </button>
  99. </div>
  100. <div class="ui menuitem">
  101. <button class="ui circular tiny icon button" onclick="redoPage();">
  102. <i class="arrow right icon"></i>
  103. </button>
  104. </div>
  105. <div class="ui menuitem">
  106. <button class="ui circular tiny icon button" onclick="refreshPage();">
  107. <i class="green refresh icon"></i>
  108. </button>
  109. </div>
  110. <div id="urlbar" class="ui tiny action fluid input">
  111. <input id="urlText" type="text" value="about:blank" onkeydown="handleURLKeydown(event);">
  112. <button id="starBtn" class="ui icon basic circular tiny button" onclick="addBookMark();">
  113. <i class="star icon"></i>
  114. </button>
  115. </div>
  116. <div class="right menu">
  117. <div class="ui rightMenuItem">
  118. <button class="ui tiny icon button sidebarToggle" onclick="toggleBookmark(this);">
  119. <i class="blue bookmark icon"></i>
  120. </button>
  121. </div>
  122. <div class="ui rightMenuItem">
  123. <button class="ui tiny icon button" onclick="loadWebsite('about:blank');">
  124. <i class="home icon"></i>
  125. </button>
  126. </div>
  127. <div class="ui rightMenuItem">
  128. <button class="ui tiny icon button" title="Open in new Window" onclick="openInNewWindow();">
  129. <i class="grey external icon"></i>
  130. </button>
  131. </div>
  132. <div class="ui rightMenuItem">
  133. <button class="ui tiny icon button sidebarToggle" title="Download Manager" onclick="toggleDownloadManager(this);">
  134. <i class="grey download icon"></i>
  135. </button>
  136. </div>
  137. </div>
  138. </div>
  139. <iframe id="xframe" src="./blank.html" allow="fullscreen" referrerpolicy="no-referrer">
  140. </iframe>
  141. <div id="bookmarkbar" class="sidebar" style="display:none;">
  142. <div class="ui container">
  143. <div id="bookmarklist" class="ui middle aligned divided list">
  144. </div>
  145. </div>
  146. </div>
  147. <div id="downloadManager" class="sidebar" style="display:none;">
  148. <div class="ui container">
  149. <div id="downloadDropper" allowdrop="true" ondrop="drop(event)" ondragstart="return false;" onclick="event.preventDefault(); openURLEnter();" ondblclick="event.preventDefault();event.stopImmediatePropagation();" ondragover="allowDrop(event);" draggable="false">
  150. <p style="margin-top: 3em;">Drop a link, image into this area; or click this area to enter a URL for download</p>
  151. </div>
  152. <div class="ui action fluid input" id="iframeMode" style="display:none;">
  153. <input type="text" id="manualInputURL" placeholder="Copy link here to download">
  154. <button class="ui icon green basic button" onclick="downloadFromManualInputURL();"><i class="ui download icon"></i></button>
  155. </div>
  156. </div>
  157. <div class="ui divider"></div>
  158. <p>Downloaded Files</p>
  159. <div class="ui list" id="downloadList" style="min-height: 300px;">
  160. </div>
  161. </div>
  162. <div id="notvdiWarning">
  163. <div class="ui yellow message">
  164. <i class="close icon"></i>
  165. <div class="header">
  166. <i class="remove icon"></i>Not Recommended Way of Usage
  167. </div>
  168. <p>Please use your native browser windows instead of this iframe browser for maximum website compatibility.</p>
  169. </div>
  170. </div>
  171. <script>
  172. let historyStack = [];
  173. let historyPopStack = [];
  174. let currentURL = "about:blank";
  175. let downloadPendingURL = "newfile.txt";
  176. let preferDownloadLocation = "user:/Desktop";
  177. let currentTitle = "";
  178. let bookmarkBuffer = [];
  179. let titleBuffer = {};
  180. //Drag drop download function
  181. function drop(ev) {
  182. ev.preventDefault();
  183. ev.stopImmediatePropagation();
  184. var target = $(ev.target);
  185. if (!(target.is("div") || target.is("a"))) {
  186. target = $(target).parent();
  187. }
  188. var linkObject = ev.dataTransfer.getData('text/html');
  189. window.debug = ev.dataTransfer;
  190. var remoteLink = ($(linkObject).find("a").length>0 || $(linkObject).is("a"))? ($(linkObject).find("a").attr("href")|| $(linkObject).attr("href")):($(linkObject).find("img").length>0?$(linkObject).find('img').attr("src"):($(linkObject).attr('src')!=""?$(linkObject).attr('src'):undefined));
  191. console.log(ev.dataTransfer, linkObject, remoteLink);
  192. if (remoteLink == undefined){
  193. return;
  194. }
  195. downloadToFile(remoteLink);
  196. }
  197. function allowDrop(ev) {
  198. ev.preventDefault();
  199. }
  200. function openURLEnter(){
  201. let url = prompt("Enter the URL to be downloaded", "http://");
  202. url = url.trim();
  203. if (url == null || url == undefined || url == "http://" || url == "https://"){
  204. return;
  205. }
  206. downloadToFile(url);
  207. }
  208. function downloadFromManualInputURL(){
  209. var url = $("#manualInputURL").val();
  210. downloadToFile(url);
  211. }
  212. function downloadToFile(downloadURL){
  213. downloadPendingURL = downloadURL;
  214. selectSaveLocation();
  215. }
  216. //save location selector
  217. function selectSaveLocation() {
  218. var filename = decodeURI(downloadPendingURL.split("/").pop().split("?").shift());
  219. option = {
  220. defaultName: filename //Default filename used in new operation
  221. }
  222. ao_module_openFileSelector(fileSelected, preferDownloadLocation, "new", false, option);
  223. }
  224. function updateDownloadProg(elementId){
  225. if ($("#" + elementId).attr("status") == "downloading"){
  226. //This task is still downloading. Get its current size and
  227. //arrange next progress update check
  228. ao_module_agirun("Browser/functions/sizechk.js", {
  229. filepath: $("#" + elementId).attr('fpath')
  230. }, function(data){
  231. console.log(data);
  232. if (data == -1){
  233. $("#" + elementId).find(".downproc").text("[Starting]");
  234. }else{
  235. $("#" + elementId).find(".downproc").text("[" + ao_module_utils.formatBytes(data, 1) + "]");
  236. }
  237. setTimeout(function(){
  238. updateDownloadProg(elementId);
  239. }, 1000);
  240. });
  241. }else{
  242. $("#" + elementId).find(".downproc").text("");
  243. }
  244. }
  245. function fileSelected(filedata) {
  246. var fileFullpath = filedata[0].filepath;
  247. var pathchunk = fileFullpath.split("/");
  248. var filename = pathchunk.pop();
  249. var filepath = pathchunk.join("/");
  250. preferDownloadLocation = filepath;
  251. //Append a download pending item to the list
  252. var thisDownloadUUID = "download_" + Date.now();
  253. $("#downloadList").append(`
  254. <div class="item downloadTask" id="${thisDownloadUUID}" fpath="${fileFullpath}" status="downloading">
  255. <i class="download icon mainicon"></i>
  256. <a class="content" onclick="openFileLocation('${filepath}', '${filename}');">
  257. ${filename} <span class="downproc"></span>
  258. </a>
  259. </div>
  260. `);
  261. updateDownloadProg(thisDownloadUUID);
  262. //Call AGI to download the file
  263. ao_module_agirun("Browser/functions/download.js", {
  264. link: downloadPendingURL,
  265. filename: filename,
  266. filepath: filepath
  267. }, function(data){
  268. console.log(data);
  269. $("#" + thisDownloadUUID).find(".mainicon").attr("class", "green checkmark icon mainicon");
  270. $("#" + thisDownloadUUID).attr("status", "done");
  271. }, function(){
  272. //Error callback
  273. $("#" + thisDownloadUUID).find(".mainicon").attr("class", "red remove icon mainicon");
  274. $("#" + thisDownloadUUID).attr("status", "error");
  275. })
  276. }
  277. function openFileLocation(filepath, filename){
  278. ao_module_openPath(filepath, filename);
  279. }
  280. function allowDragAndDrop(enable=false){
  281. if(enable){
  282. $("#downloadDropper").show();
  283. $("#iframeMode").hide();
  284. }else{
  285. $("#downloadDropper").hide();
  286. $("#iframeMode").show();
  287. }
  288. }
  289. //Check if currently under vdi mode
  290. if (ao_module_virtualDesktop == false){
  291. $("#notvdiWarning").show();
  292. }
  293. $('.message .close').on('click', function() {
  294. $(this).closest('.message').transition('fade');
  295. });
  296. function toggleBookmark(object){
  297. $(".sidebar:not(#bookmarkbar)").hide();
  298. var isVisable = $("#bookmarkbar").is(":visible")
  299. $("#bookmarkbar").fadeToggle('fast');
  300. $(".sidebarToggle.selected").removeClass("selected");
  301. if (!isVisable){
  302. $(object).addClass('selected');
  303. }
  304. }
  305. function toggleDownloadManager(object){
  306. $(".sidebar:not(#downloadManager)").hide();
  307. var isVisable = $("#downloadManager").is(":visible")
  308. $("#downloadManager").fadeToggle('fast');
  309. $(".sidebarToggle.selected").removeClass("selected");
  310. if (!isVisable){
  311. $(object).addClass('selected');
  312. }
  313. }
  314. //Perform window resize element size calculation
  315. $(window).on("resize", function(){
  316. updateResizeElements();
  317. });
  318. function updateResizeElements(){
  319. let buttonWidths = 0;
  320. $(".menuitem").each(function(){
  321. buttonWidths+= $(this).width();
  322. });
  323. let urlbarWidth = window.innerWidth - buttonWidths - 20;
  324. $("#urlbar").css("width", urlbarWidth + "px");
  325. }
  326. updateResizeElements();
  327. function handleURLKeydown(e){
  328. if (e.keyCode == 13){
  329. let url = $("#urlText").val();
  330. loadWebsite(url);
  331. }
  332. }
  333. function refreshPage(){
  334. loadWebsite(currentURL);
  335. }
  336. function undoPage(){
  337. //Push current page into the history pop stack
  338. if (historyStack.length == 0){
  339. return;
  340. }
  341. let currentBackupURL = currentURL;
  342. historyPopStack.push(currentBackupURL);
  343. let targetReturnURL = historyStack.pop();
  344. loadWebsite(targetReturnURL, false);
  345. }
  346. function redoPage(){
  347. if (historyPopStack.length == 0){
  348. return;
  349. }
  350. restorePage = historyPopStack.pop();
  351. historyStack.push(currentURL);
  352. loadWebsite(restorePage, false);
  353. }
  354. function openInNewWindow(){
  355. window.open(currentURL);
  356. }
  357. function getTitleFromURL(targetURL){
  358. var title = targetURL;
  359. if (targetURL.includes("//")){
  360. title = targetURL.substr(targetURL.indexOf("/") + 2, targetURL.length);
  361. }
  362. return title;
  363. }
  364. function loadWebsite(targetURL, writeRestoreRecord = true){
  365. if (writeRestoreRecord && currentURL != targetURL){
  366. historyPopStack = [];
  367. }
  368. //Handle special case
  369. if (targetURL == "about:blank"){
  370. $("#xframe").removeAttr("srcdoc");
  371. $("#xframe").attr("src", "blank.html");
  372. $("#urlText").val(targetURL);
  373. $("#toolbar").removeClass("proxy");
  374. if (writeRestoreRecord && currentURL != targetURL){
  375. historyStack.push(JSON.parse(JSON.stringify(currentURL)));
  376. }
  377. currentURL = targetURL;
  378. allowDragAndDrop(false);
  379. return;
  380. }
  381. //Remove the tailing / if exists
  382. if (targetURL.substr(targetURL.length - 1, targetURL.length) == "/"){
  383. targetURL = targetURL.substr(0, targetURL.length - 1);
  384. }
  385. $("#xframe").removeAttr("srcdoc");
  386. $("#xframe").attr("src", "loading.html");
  387. //Filter the URL if required
  388. if (targetURL.substr(0,4) != "http"){
  389. if (location.protocol !== "https:"){
  390. //This page is currently loaded in http mode. Add http:// to it
  391. targetURL = "http://" + targetURL;
  392. }else{
  393. targetURL = "https://" + targetURL;
  394. }
  395. }
  396. $("#urlText").val(targetURL);
  397. if (writeRestoreRecord && currentURL != targetURL){
  398. historyStack.push(JSON.parse(JSON.stringify(currentURL)));
  399. }
  400. currentURL = targetURL;
  401. //Check if the website allow iframe
  402. checkIfAllowIframing(targetURL, function(allowIframe, redirectTarget){
  403. if (allowIframe == null){
  404. $("#xframe").attr("src", "notfound.html#" + targetURL);
  405. }else{
  406. if (allowIframe == true){
  407. allowDragAndDrop(false);
  408. $("#xframe").removeAttr("srcdoc");
  409. $("#xframe").attr("src", targetURL);
  410. $("#toolbar").removeClass("proxy");
  411. $("#xframe").on("load", function(){
  412. //Get the page title
  413. ao_module_agirun("Browser/functions/getTitle.js", {url: targetURL}, function(data){
  414. if (data == ""){
  415. let title = getTitleFromURL(targetURL);
  416. ao_module_setWindowTitle(title);
  417. currentTitle = title;
  418. }else{
  419. ao_module_setWindowTitle(data);
  420. currentTitle = data;
  421. }
  422. });
  423. $("#xframe").off("load");
  424. });
  425. }else{
  426. allowDragAndDrop(true);
  427. proxyWebContent(targetURL, function(content){
  428. $("#xframe").attr("src", "");
  429. $("#xframe").attr("srcdoc", content);
  430. $("#toolbar").addClass("proxy");
  431. //Extract the title
  432. var matches = content.match(/<title>(.*?)<\/title>/);
  433. if (matches == null){
  434. let title = getTitleFromURL(targetURL);
  435. ao_module_setWindowTitle(title);
  436. currentTitle = title;
  437. }else{
  438. var title = matches[0].replace(/(<([^>]+)>)/gi, "");
  439. ao_module_setWindowTitle(title);
  440. currentTitle = title;
  441. }
  442. });
  443. //alert("Target website do not allow embedding");
  444. }
  445. if (redirectTarget != undefined && redirectTarget != ""){
  446. $("#urlText").val(redirectTarget);
  447. }
  448. }
  449. });
  450. updateBookmarkButtonColor();
  451. }
  452. function updateBookmarkButtonColor(){
  453. if (insideBookmark(currentURL)){
  454. $("#starBtn").addClass("yellow");
  455. }else{
  456. $("#starBtn").removeClass("yellow");
  457. }
  458. }
  459. function initbookmark(){
  460. ao_module_agirun("Browser/functions/bookmark.js", {opr: "read"}, function(bookmarkData){
  461. bookmarkBuffer = bookmarkData;
  462. console.log("BOOKMARK DATA", bookmarkData);
  463. ao_module_agirun("Browser/functions/bookmark.js", {rtype: "titles", opr: "read"}, function(data){
  464. titleBuffer = data;
  465. $("#bookmarklist").html("");
  466. bookmarkBuffer.forEach(bookmark => {
  467. let matchingTitle = titleBuffer[bookmark];
  468. if (matchingTitle == undefined){
  469. matchingTitle = getTitleFromURL(bookmark);
  470. }
  471. //Render the bookmark table
  472. $("#bookmarklist").append(`
  473. <div class="item">
  474. <div class="right floated content">
  475. <div class="ui mini icon basic blue circular button" onclick="loadWebsite('${bookmark}');"><i class="ui linkify icon"></i></div>
  476. </div>
  477. <div class="content">
  478. ${matchingTitle}
  479. </div>
  480. </div>`);
  481. });
  482. if (bookmarkBuffer.length == 0){
  483. $("#bookmarklist").append(`<div class="item">
  484. <div class="content">
  485. <i class="ui bookmark icon"></i> No bookmark saved
  486. </div>
  487. </div>`);
  488. }
  489. });
  490. });
  491. }
  492. initbookmark();
  493. function addBookMark(){
  494. if (bookmarkBuffer.includes(currentURL)){
  495. //Remove bookmark
  496. bookmarkBuffer = bookmarkBuffer.filter(e => e !== currentURL);
  497. delete(titleBuffer[currentURL]);
  498. }else{
  499. //Add bookmark
  500. bookmarkBuffer.push(currentURL);
  501. //Remove array in the table
  502. bookmarkBuffer = bookmarkBuffer.filter(function(item, pos, self) {
  503. return self.indexOf(item) == pos;
  504. });
  505. titleBuffer[currentURL] = currentTitle;
  506. }
  507. ao_module_agirun("Browser/functions/bookmark.js", {"opr": "write", "newBookmarkArray": JSON.stringify(bookmarkBuffer)}, function(data){
  508. ao_module_agirun("Browser/functions/bookmark.js", {"rtype": "titles", "opr": "write", "newTitleArray": JSON.stringify(titleBuffer)}, function(data){
  509. console.log(data);
  510. updateBookmarkButtonColor();
  511. initbookmark();
  512. });
  513. });
  514. }
  515. function insideBookmark(url){
  516. return bookmarkBuffer.includes(url);
  517. }
  518. function proxyWebContent(url, callback){
  519. ao_module_agirun("Browser/functions/proxy.js", {
  520. url: url,
  521. }, function(data){
  522. callback(data);
  523. });
  524. }
  525. //Check if a website can be directly embedded as iframe, can return true / false /null (site not exists)
  526. function checkIfAllowIframing(url, callback){
  527. ao_module_agirun("Browser/functions/getHeader.js", {
  528. url: url
  529. }, function(data){
  530. let xFrameOptions = JSON.parse(data);
  531. let header = "";
  532. if (xFrameOptions.header == null){
  533. xFrameOptions.header = "deny";
  534. }else{
  535. header = xFrameOptions.header.toLowerCase().trim();
  536. }
  537. let location = xFrameOptions.location;
  538. if (header == "null"){
  539. //Site not exists
  540. callback(null);
  541. }
  542. if (header == "sameorigin" || header == "deny"){
  543. //This webpage do not allow iframeing
  544. callback(false, location);
  545. }else{
  546. //This webpage allow iframing. Show it
  547. callback(true, location);
  548. }
  549. }, undefined, 5000)
  550. }
  551. </script>
  552. </body>
  553. </html>