diff --git a/Chrome Extension/src/bg/background.js b/Chrome Extension/src/bg/background.js index cf02da9..e5cb6ca 100644 --- a/Chrome Extension/src/bg/background.js +++ b/Chrome Extension/src/bg/background.js @@ -5,15 +5,13 @@ // }); -//example of using a message handler from the inject scripts -chrome.extension.onMessage.addListener( - function(request, sender, sendResponse) { - chrome.pageAction.show(sender.tab.id); - sendResponse(); - }); +// example of using a message handler from the inject scripts - chrome.runtime.onInstalled.addListener(function (object) { - chrome.tabs.create({url: "views/instructions.html"}, function (tab) { - console.log("New tab launched with http://yoursite.com/"); - }); +// chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { +// chrome.pageAction.show(sender.tab.id); +// sendResponse(); +// }); + +chrome.runtime.onInstalled.addListener(function(object) { + chrome.tabs.create({url: 'views/instructions.html'}); }); diff --git a/Chrome Extension/src/inject/inject.js b/Chrome Extension/src/inject/inject.js index ced2f15..239c8fc 100644 --- a/Chrome Extension/src/inject/inject.js +++ b/Chrome Extension/src/inject/inject.js @@ -1,76 +1,186 @@ chrome.extension.sendMessage({}, function(response) { - var readyStateCheckInterval = setInterval(function() { - if (document.readyState === "complete") { - clearInterval(readyStateCheckInterval); - - // ---------------------------------------------------------- - // This part of the script triggers when page is done loading - console.log("Hello. This message was sent from scripts/inject.js"); - // ---------------------------------------------------------- - - function getEmbedLocation() { - return document.getElementsByClassName("punch-full-screen-element")[0]; - } - - function embed(url) { - var ifrm = document.createElement('iframe'); - ifrm.setAttribute('id', 'circuitverse-iframe'); // assign an id - ifrm.setAttribute('class', 'circuitverse-iframe'); // assign an id - ifrm.setAttribute('style','width:100%;height:100%;position:fixed;top:0px;left:0px;z-index:100') - - var location = getEmbedLocation(); - if(location == undefined) {return;} - location.appendChild(ifrm); // to place at end of document - - // assign url - ifrm.setAttribute('src', url); - iframe_embedded = true; - url_embedded = url; - } - - function removeEmbed() { - if(!iframe_embedded)return; - iframe_embedded = false; - var iframe = document.getElementById('circuitverse-iframe'); - if(iframe) iframe.parentNode.removeChild(iframe); - } - - var url_embedded = undefined; - var iframe_embedded = false; - - function main() { - var iframes = document.getElementsByClassName('punch-present-iframe'); - if(iframes.length == 0) { - removeEmbed(); - return; - } - iframeDocument = iframes[0]; - if(iframe_embedded) { - document.getElementsByClassName("punch-present-iframe")[0].contentWindow.document.body.focus(); - } - - var text = iframeDocument.contentWindow.document.body.innerHTML; - - var re = /xlink:href="([(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*))"/g; - var m; - do { - m = re.exec(text); - if (m) { - var url = m[1]; - if(url.includes("circuitverse.org")) { - if(iframe_embedded && url_embedded == url) return; - if(iframe_embedded && url_embedded != url) { - removeEmbed(); - } - embed(url); - return; - } - } - } while (m); - removeEmbed(); - } - - setInterval(main, 1000); - } - }, 10); -}); \ No newline at end of file + var readyStateCheckInterval = setInterval(function() { + if (document.readyState === 'complete') { + clearInterval(readyStateCheckInterval); + + // Specific location which allows the iframe embedded to be visible + function getEmbedLocation() { + return document.getElementsByClassName('punch-full-screen-element')[0]; + } + + // Extract slide number from the presentation + function getSlideNumber() { + var labelElement = + getSlideIframe().contentWindow.document.getElementsByClassName( + 'punch-viewer-svgpage-a11yelement')[0]; + if (!labelElement) return -1; + var label = labelElement.getAttribute('aria-label'); + return parseInt(label.match(/Slide (\d*)/)[1]); + } + + // Get the slide iframe + function getSlideIframe() { + return document.getElementsByClassName('punch-present-iframe')[0]; + } + + // Get the slide dimensions in svg values + function getSlideSvgViewDimensions() { + var svgContainer = + getSlideIframe().contentWindow.document.getElementsByClassName( + 'punch-viewer-svgpage-svgcontainer')[0]; + var svg = svgContainer.children[0]; + var viewBox = svg.getAttribute('viewBox').split(' '); + + return { + slideW: parseFloat(viewBox[2]), slideH: parseFloat(viewBox[3]) + } + } + + // Extract position and size of embedded image in SVG + // Needed for identifying exact location to embed the iframe + function extractPositionFromPath(path) { + var svgLinkPath = path.getAttribute('d'); + var pathRegexExp = + /M ([\S]*) ([\S]*) L ([\S]*) ([\S]*) ([\S]*) ([\S]*) ([\S]*) ([\S]*) Z/; + var matches = svgLinkPath.match(pathRegexExp); + var x1 = parseFloat(matches[1]); + var y1 = parseFloat(matches[2]); + var x2 = parseFloat(matches[3]); + var y3 = parseFloat(matches[6]); + var widthInSvg = x2 - x1; + var heightInSvg = y3 - y1; + return { + svgX: x1, svgY: y1, svgW: widthInSvg, svgH: heightInSvg + } + } + + // Get slide dimensions and offsets in pixels + function getSlideDimensions() { + var slideDiv = + getSlideIframe().contentWindow.document.getElementsByClassName( + 'punch-viewer-content')[0]; + var metadata = { + xOffsetPx: parseFloat(slideDiv.style.left), + yOffsetPx: parseFloat(slideDiv.style.top), + slideWidthPx: parseFloat(slideDiv.style.width), + slideHeightPx: parseFloat(slideDiv.style.height), + }; + return metadata; + } + + // Create circuitverse iframe from anchor tag + // Calculates exact position and places the iframe + function createEmbedIframe(anchorTag) { + var url = + anchorTag.getAttributeNS('http://www.w3.org/1999/xlink', 'href'); + var {svgX, svgY, svgW, svgH} = + extractPositionFromPath(anchorTag.children[0]); + var {slideW, slideH} = getSlideSvgViewDimensions(); + var {xOffsetPx, yOffsetPx, slideWidthPx, slideHeightPx} = + getSlideDimensions(); + + var svg2px = slideWidthPx / slideW; + + var absoluteXoffSetPx = xOffsetPx + svgX * svg2px; + var absoluteYoffSetPx = yOffsetPx + svgY * svg2px; + var widthPx = svgW * svg2px; + var heightPx = svgH * svg2px; + absoluteXoffSetPx = Math.round(absoluteXoffSetPx); + absoluteYoffSetPx = Math.round(absoluteYoffSetPx); + widthPx = Math.round(widthPx); + heightPx = Math.round(heightPx); + + var ifrm = document.createElement('iframe'); + ifrm.classList.add('circuitverse-iframe'); // assign a class + ifrm.setAttribute('style', `position:fixed;z-index:100; + width:${widthPx}px; + height:${heightPx}px; + top:${absoluteYoffSetPx}px; + left:${absoluteXoffSetPx}px`); + // assign url + ifrm.setAttribute('src', url); + + return ifrm; + } + + // Embeds iframe given link + function embed(anchorTag) { + var iframe = createEmbedIframe(anchorTag); + var url = + anchorTag.getAttributeNS('http://www.w3.org/1999/xlink', 'href'); + var location = getEmbedLocation(); + if (location == undefined) { + return; + } + location.appendChild(iframe); // to place at end of document + + iframe_embedded = true; + url_embedded = url; + } + + // Removes all embedded iframes + function removeEmbed() { + var iframes = document.getElementsByClassName('circuitverse-iframe'); + while (iframes[0]) { + iframes[0].parentNode.removeChild(iframes[0]); + } + } + + // Keeps track of current frame + + var slideNumber = -1; + + // Setting slideNumber = -1 will reset everything + function reset() { + slideNumber = -1; + } + + // Driver logic + function main() { + var iframeDocument = getSlideIframe(); + + if (!iframeDocument) { + slideNumber = -1; + removeEmbed(); + return; + } + + // Bring slide into focus - necessary for slide transitions to work! + if (slideNumber != -1) { + iframeDocument.contentWindow.document.body.focus(); + } + + if (slideNumber == getSlideNumber()) return; + + // New Slide + removeEmbed(); // remove previous iframes + slideNumber = getSlideNumber(); + + var anchorTags = + iframeDocument.contentWindow.document.getElementsByTagName('a'); + + var prevUrl = undefined; + for (var i = 0; i < anchorTags.length; i++) { + var url = anchorTags[i].getAttributeNS( + 'http://www.w3.org/1999/xlink', 'href'); + + // Google Slides has 2 anchor tags for every link for some reason; + // Hence ensuring no duplicate embeds! + if (url != prevUrl && url.includes('circuitverse.org')) { + prevUrl = url + embed(anchorTags[i]); + } + } + } + + // Call driver logic repeatedly + setInterval(main, 300); + + // Force reset after 3 seconds - needed for window resizing + // Also needed if first slide has circuit + window.addEventListener('resize', () => { + setTimeout(reset, 3000); + }); + } + }, 10); +});