UNPKG

remoteview

Version:

Remote isolated browser platform by Dosyago

436 lines (408 loc) 11.8 kB
import {deviceIsMobile} from '../common.js'; import {d as R, u as X} from '../../node_modules/dumbass/r.js'; import {openModal} from './index.js'; const CLOSE_DELAY = 222; const SHORT_CUT = 'Ctrl+Shift+J'; //const FUNC = e => console.log("Doing it", e); const CONTEXT_MENU = (state) => ({ 'page': [ { title: 'Open link in new tab', shortCut: SHORT_CUT, func: openInNewTab, }, { title: 'Save screenshot', shortCut: SHORT_CUT, func: download }, { title: 'Reload', shortCut: SHORT_CUT, func: reload }, { title: 'Copy text from here', shortCut: SHORT_CUT, func: copy, hr: true }, { title: 'Copy link address from here', shortCut: SHORT_CUT, func: copyLink, }, { title: 'Paste text', shortCut: SHORT_CUT, func: paste }, // This is blocked (apparently) on: https://bugs.chromium.org/p/chromium/issues/detail?id=1015260 { title: 'New incognito tab', shortCut: SHORT_CUT, func: newBrowserContextAndTab, hr: true, }, { title: 'Clear history', shortCut: SHORT_CUT, func: clearHistoryAndCacheLeaveCookies, hr: true, }, { title: 'Wipe everything', shortCut: SHORT_CUT, func: clearBrowsingData, }, { title: ( document.fullscreenElement || document.webkitFullscreenElement ) ? 'Exit full screen' : 'Full screen' , shortCut: SHORT_CUT, func: fullScreen, hr: true, }, ], }); export function ContextMenu(/*state*/) { return R` `; } export const CTX_MENU_THRESHOLD = 675; export function makeContextMenuHandler(state, node = {type:'page', id: 'current-page'}) { const {/*id, */ type:nodeType} = node; return contextMenu => { const menuItems = CONTEXT_MENU(state)[nodeType]; // we need this check because we attach a handler to each node // we could use delegation at the container of the root node // but for now we do it like this if ( navigator.vibrate ) { try { navigator.vibrate(100); } catch(e) { console.warn('error vibrating', e); } } if ( contextMenu.currentTarget.contains(contextMenu.target) ) { let pageX, pageY; if ( contextMenu.pageX && contextMenu.pageY ) { ({pageX, pageY} = contextMenu); } else { const {clientX, clientY} = contextMenu.detail; ({pageX, pageY} = contextMenu.detail); Object.assign(contextMenu, {pageX, pageY, clientX, clientY}); } // cancel click for chrome mobile // (note: this does not work as intended. // It does not cancel a touch click on contextmenu open) // so it's commented out // state.H({type:'touchcancel'}); // the actual way to kill the click is // by killing the next mouse release like so: state.viewState.contextMenuClick = contextMenu; //state.viewState.killNextMouseReleased = true; // we also stop default context menu contextMenu.preventDefault(); contextMenu.stopPropagation(); const bondTasks = [ el => { // only have 1 context menu at a time close(state, false); state.viewState.contextMenu = el; }, () => self.addEventListener('click', function remove(click) { // if we clicked outside the menu, // remove the menu and stop listening for such clicks if ( ! click.target.closest('.context-menu') ) { close(state, false); self.removeEventListener('click', remove); } }), el => { const x = pageX + 'px'; const y = pageY + 'px'; if ( pageX + el.scrollWidth > innerWidth ) { el.style.right = '8px'; } else { el.style.left = x; } if ( pageY + el.scrollHeight > ( innerHeight - 32 )) { el.style.bottom = '48px'; } else { el.style.top = y; } } ]; const menuView = X` <aside class=context-menu role=menu bond=${bondTasks} contextmenu=${e => ( /* don't trigger within the menu */ e.preventDefault(), e.stopPropagation() )} > <h1>Menu</h1> <hr> <ul> ${menuItems.map(({title,func,hr}) => X` ${hr?X`<hr>`:''} <li click=${click => func(click, state)}>${title}</li> `)} </ul> </aside> `; menuView.to(contextMenu.currentTarget, 'afterEnd'); } }; } function close(state, delay = true) { if ( delay ) { setTimeout(() => { if ( state.viewState.contextMenu ) { state.viewState.contextMenu.remove(); state.viewState.contextMenu = null; } }, CLOSE_DELAY); } else { if ( state.viewState.contextMenu ) { state.viewState.contextMenu.remove(); state.viewState.contextMenu = null; } } } /*function styleContextMenu(el, state) { return ` * .context-menu { position: absolute; background: whitesmoke; box-shadow: 1px 1px 1px 1px grey; padding: 0.5em 0; min-width: 200px; z-index: 10; } * .context-menu h1 { margin: 0; font-size: smaller; } * .context-menu ul { margin: 0; padding: 0; font-size: smaller; } * .context-menu ul li { cursor: default; } * .context-menu li, * .context-menu h1 { padding: 0 1em; } * .context-menu ul li:hover { background: powderblue; } `; }*/ // context menu option functions /** * This code is needed like this * so that paste works in mobile (Chrome) and on Desktop (Chrome and Firefox) * ****/ function copy(click, state) { const contextClick = state.viewState.contextMenuClick; const {clientX,clientY, target} = contextClick; const {H} = state; close(state); state.elementInfoContinuation = ({innerText, noSuchElement}) => { if ( ! noSuchElement ) { state.elementInfoContinuation = null; openModal({modal:{type:'infobox', message: innerText, title: 'Text from page:'}}, state); } }; H({ type: 'getElementInfo', synthetic: true, data: { innerText: true, target, clientX, clientY } }); } function copyLink(click, state) { const contextClick = state.viewState.contextMenuClick; const {clientX,clientY, target} = contextClick; const {H} = state; close(state); state.elementInfoContinuation = ({attributes, noSuchElement}) => { if ( ! noSuchElement ) { state.elementInfoContinuation = null; openModal({modal:{type:'infobox', message: attributes.href, title: 'Text from page:'}}, state); } }; H({ type: 'getElementInfo', synthetic: true, data: { closest: 'a[href]', attributes: ['href'], target, clientX, clientY } }); } function paste(click, state) { close(state); const pasteData = prompt("Enter text to paste"); const input = state.viewState.shouldHaveFocus; if ( ! input ) return; const value = input.value; const newValue = value.slice(0, input.selectionStart) + pasteData + value.slice(input.selectionEnd); input.value = newValue; input.selectionStart = input.selectionEnd; if ( document.activeElement !== input ) { input.focus(); } state.H({ type: 'typing', data: pasteData, synthetic: true, event: {paste: true, simulated: true} }); } function download(click, state) { close(state); const timeNow = new Date(); const stringTime = timeNow.toJSON(); const fileName = stringTime.replace(/[-:.]/g, "_"); const imageData = state.viewState.canvasEl.toDataURL(); const downloader = document.createElement('a'); downloader.href = imageData; Object.assign(downloader.style, { position: 'absolute', top: '0px', left: '0px', opacity: 0 }); downloader.download = `${fileName}.png`; document.body.appendChild(downloader); downloader.click(); downloader.remove(); } function reload(/*click, state*/) { const goButton = document.querySelector('form.url button.go'); goButton.click(); } function openInNewTab(click, state) { const contextClick = state.viewState.contextMenuClick; const {target,pageX,pageY,clientX,clientY} = contextClick; const {H} = state; state.viewState.killNextMouseReleased = false; if ( deviceIsMobile() ) { // we need to get the URL of the target link // then use // state.createTab(click, url); state.elementInfoContinuation = ({attributes, noSuchElement}) => { if ( ! noSuchElement ) { state.elementInfoContinuation = null; state.createTab(click, attributes.href); } }; H({ type: 'getElementInfo', synthetic: true, data: { closest: 'a[href]', attributes: ['href'], target, clientX, clientY } }); } else { H({ type: 'pointerdown', button: 0, ctrlKey: true, target, pageX, pageY, clientX, clientY, noShot: true }); H({ type: 'pointerup', button: 0, ctrlKey: true, target, pageX, pageY, clientX, clientY }); } close(state); } function newBrowserContextAndTab(click, state) { const {H} = state; H({ synthetic: true, type: 'newIncognitoTab', data: {} }); close(state); } function clearHistoryAndCacheLeaveCookies(click, state) { const doIt = confirm("You'll stay signed in to most sites, but wipe browsing history and cached files. Are you sure?"); if ( doIt ) { const {H} = state; H({ synthetic: true, type: "clearAllPageHistory" }); H({ synthetic: true, type: "clearCache" }); alert("Cleared all caches and history."); } close(state); } function clearBrowsingData(click, state) { const doIt = confirm("This will sign you out of most sites, and wipe all history and caches. Really wipe everything?"); if ( doIt ) { const {H} = state; H({ synthetic: true, type: "clearAllPageHistory" }); H({ synthetic: true, type: "clearCache" }); H({ synthetic: true, type: "clearCookies" }); alert("Cleared all history, caches and cookies."); } close(state); } async function fullScreen(click, state) { if ( document.fullscreenElement || document.webkitFullscreenElement ) { if ( document.webkitCancelFullscreen ) { document.webkitCancelFullscreen(); } else { await document.exitFullscreen(); } } else { if ( document.body.webkitRequestFullscreen ) { document.body.webkitRequestFullscreen({navigationUI:'hide'}); } else { await document.body.requestFullscreen({navigationUI:'hide'}); } } close(state); }