I'm trying to do an extension with this behaviour: When I click the extension button, it parses the current page and executes a function for each found item.
background.js
function downloadPage(urlpage,name){
chrome.tabs.create({
url: urlpage
}, function(tab) {
chrome.tabs.onUpdated.addListener(function func(tabId, changeInfo) {
if (tabId == tab.id && changeInfo.status == 'complete') {
chrome.tabs.onUpdated.removeListener(func);
savePage(tabId, name);
}
});
});
}
function savePage(tabId, name) {
chrome.pageCapture.saveAsMHTML({
tabId: tabId
}, function(blob) {
var url = URL.createObjectURL(blob);
// Optional: chrome.tabs.remove(tabId); // to close the tab
chrome.downloads.download({
url: url,
filename: 'prueba/test.mht'
});
});
}
popup.js
var candidatos = document.getElementsByClassName("user-name");
if(candidatos != null){
for (i = 0; i < candidatos.length; i++) {
chrome.extension.getBackgroundPage().downloadPage(candidatos[i].href, candidatos[i].text)
}
}
manifest.json
{
"manifest_version": 2,
"name": "Test",
"description": "Test",
"version": "1.0",
"author": "Pablo",
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"permissions": [
"activeTab",
"tabs",
"webNavigation",
"contextMenus",
"downloads",
"<all_urls>",
"pageCapture"
]
}
I can't get to run anything when I click the extension button.
Thanks in advance.
Updated:
I have run your code at my end, when you are saying "I can't get to run anything when I click the extension button", I guess you mean savePage function in not called. That is because in your callback for chrome.tabs.create function, you register chrome.tabs.onUpdated listener, it is too late. I recommend you just use the tab you get from the callback and do what you want.
function downloadPage(urlpage,name){
chrome.tabs.create({
url: urlpage
}, function(tab) {
savePage(tab.id, name);
});
}
It's recommended to read the Official Guide especially browser action. The basic part should be registering the browser action listener and listening to onClicked event.
chrome.browserAction.onClicked.addListener(function () {
// Do what you want when clicking the 'extension icon'
});
Your fundamental problem is that popup.js refers to the DOM of popup.html, which doesn’t have the class or code that you need. In order to interact with the DOM, you need a content script. The best approach for that would be to have the background page programmatically inject it. Then you can also use the callback parameters to avoid having to use message passing:
chrome.tabs.query({active:true},function(tabArray){
chrome.tabs.executeScript(tabArray[0].id,
{code:"document.getElementsByClassName('user-name');"},
allFrames);
});
// the parameter to allFrames is the result of code in each frame of the tab
function allFrames(frames) {
for ( var i = 0 ; i < frames.length ; i++ ) {
var classes = frames[i];
for ( var j = 0 ; j < classes.length ; j++ ) {
downloadPage(classes[j].href,classes[j].text);
}
}
}
#HaibaraAi’s point is that if the only purpose of the popup is to trigger an action in the background, then it’s much easier to omit the popup and simply have:
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(tab.id,
{code:"document.getElementsByClassName('user-name');"},
allFrames);
});
And because tabs.create gives you a completely loaded tab, you don’t need to listen for its completion. So we can stick with #HaibaraAi’s version of that function. The completed example is therefore
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(tab.id,
{code:"document.getElementsByClassName('user-name');"},
allFrames);
});
function allFrames(frames) {
for ( var i = 0 ; i < frames.length ; i++ ) {
var classes = frames[i];
for ( var j = 0 ; j < classes.length ; j++ ) {
downloadPage(classes[j].href,classes[j].text);
}
}
}
function downloadPage(urlpage,name){
chrome.tabs.create({
url: urlpage
}, function(tab) {
savePage(tab.id, name);
});
}
With your original savePage. And you no longer need the popup. (Also, the activeTab permission is contained in the tabs permission, so you don’t need that.)
Related
For my Chrome Extension,
I'm searching multiple classes/id's to then include their text with some HTML I'm inserting into the website.
When I run the extension,
the HTML widget I'm inserting is inserted multiple times but should only be done once.
I realize MutationObserver is intended to check for every DOM change and seemingly inserts my widget every time that happens I presume. However, I'm not sure how best to approach making sure the widget is only inserted once.
I tried using observer.disconnect(); which helps on initial load to insert the widget once but then it won't re-insert the widget on return to the page – will only on refresh. Couldn't figure out a way to turn observe back on.
Not sure if disconnect and reconnect is even a smart path but it's what I came up with.
In the attached screenshot, you can see it keeps adding the widget with DOM changes.
How should I approach fixing this?
Thank you!
Link to download the extension
manifest.json
{
"manifest_version": 3,
"name": "Test SO Mutation Answer",
"description": "Example for StackOverflow",
"version": "0.0.1",
"host_permissions": ["<all_urls>"],
"permissions": ["storage", "activeTab", "scripting", "tabs", "webNavigation"],
"content_scripts": [
{
"matches": ["https://www.zillow.com/*"],
"js": ["ballpark.js"],
"css": ["main.css"]
}
],
"web_accessible_resources": [
{
"resources": ["/images/*"],
"matches": ["<all_urls>"]
}
]
}
ballpark.js
const observer = new MutationObserver(function() {
if (document.getElementsByClassName('ds-action-bar')[0]) {
if (document.querySelector('[data-testid="price"] span') && document.querySelector('.ds-expandable-card .eUxMDw')) {
console.log("All data found");
startWidget();
if (document.getElementById('ballpark-container')) {
console.log("Do nothing, BP exists");
} else {
console.log("Adding BP...");
insertWidget();
}
} else {
console.log("Cannot find all data");
}
} else {
offerPrice = undefined;
monthlyPITI = undefined;
}
})
const target = document.querySelector('body');
const config = { childList: true };
observer.observe(target, config);
var offerPrice;
var monthlyPITI;
function startWidget() {
getAskingPrice();
getExpenses();
}
// Get Price from Zillow
function getAskingPrice() {
var askingPrice = document.querySelector('[data-testid="price"] span');
if(askingPrice !== null) {
offerPrice = parseFloat(askingPrice.innerText.replace(/\$|,/g, ''));
console.log(offerPrice + " Offer Price");
} else {
console.log("Null Asking Price Div");
}
}
function getExpenses() {
var monthlyPITIZillow = document.querySelector('.ds-expandable-card .eUxMDw');
if (monthlyPITIZillow !== null) {
monthlyPITI = parseFloat(monthlyPITIZillow.innerText.replace(/\$|,/g, ''));
console.log(monthlyPITI + " PITI");
} else {
console.log("Null Monthly PITI Div");
}
}
// Find Zillow widget to insert the extension widget
function insertWidget() {
const select_div_for_bpd = document.querySelector('div.Spacer-c11n-8-65-2__sc-17suqs2-0');
if(select_div_for_bpd !== null) {
const ballpark_container = document.createElement("div");
ballpark_container.setAttribute("id", "ballpark-container");
select_div_for_bpd.appendChild(ballpark_container);
ballpark_container.innerHTML = `
<div class="ballpark-roi-container">
<div><h1>${offerPrice}</h1> Offer Price</div>
<div><h1>${monthlyPITI}</h1> Monthly PITI</div>
</div>
`;
} else {
console.log("Cannot insert your widget");
}
}
Update
It seems like webpack is causing the issues.
If I replace the dist/background.js with:
console.log("background is running"); // Now visible ✅
const handler = (req, sender, sendResponse) => {
switch (req.type) {
case "message":
sendResponse({ data: "hi" });
break;
default:
break;
}
};
chrome.runtime.onMessage.addListener(handler);
Both the console log (in service worker) and response (in popup) are observed. Also, there are no errors.
Time to investigate further
Update #2
Upon further inspection, I noticed that the webpack output is wrapped in a function, but never called:
{
/***/ "./src/background.ts":
/*!***************************!*\
!*** ./src/background.ts ***!
\***************************/
/***/ (function () {
console.log("background is running");
const handler = (req, sender, sendResponse) => {
switch (req.type) {
case "message":
sendResponse({ data: "hi" });
break;
default:
break;
}
};
chrome.runtime.onMessage.addListener(handler);
/***/
}) // <==== adding () will make it an IIFE and everything works!
},
Question is how to automate this?
Update #3
Seems like the IIFE trick I mentioned above only works when there are no imports in background.js. As soon as I add any import, I get an error that the background script is not valid.
Adding module type property to background does not help:
// manifest.json
{
...
"background": {
"service_worker": "background.js",
"type": "module"
},
...
}
Update #4
Turns out this was caused by vendor splitting optimization in webpack:
// webpack.config.json
{
...
optimization: {
runtimeChunk: "single",
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
enforce: true,
chunks: "all",
},
},
},
},
...
}
Once I removed this, everything started working properly!
Would be nice to keep this around, but it is just an optimization after all, so if it breaks things, best to get rid of it.
How I figured this out? Well as I mentioned, everything worked a couple of commits ago. Back then I didn't have this optimization, so I tried removing it again, and everything started working again like magic.
Original Question
I had this working previously, so I am sure my setup is correct, but regardless of what I try, I now get
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
Also, I cannot find a solution online which indicates anything that differs from my setup.
Here is a MWE
dist folder structure:
dist/background.js
dist/index.html
dist/manifest.json
dist/popup.js
dist/runtime.js
dist/vendors.js
other misc files
// manifest.json
{
...
"background": {
"service_worker": "background.js"
},
...
}
// src/components/App.tsx
export default function App(): JSX.Element {
...
useEffect(() => {
chrome.runtime.sendMessage({ type: 'someMessage' }, ({ data }) => {
console.log(data);
});
}, []);
...
}
// src/background.ts
import { TSentResponse } from "./typings/background";
import { executeResponse } from "./utils/background";
console.log('sanity check'); // <=== does not fire 🤔
// also doesn't seem to be called 😥
const handleMessage = (req: { type: string }, sender: chrome.runtime.MessageSender, res: (response?: unknown) => void) => {
switch (req.type) {
case 'someMessage':
// an IIFE (worked fine before)
break;
default:
break;
}
return true; // due to asynchronous nature
};
chrome.runtime.onMessage.addListener(handleMessage);
My service worker is registered properly:
When I said above that I cannot see the logs for background, I mean when I check in the service worker dev tools (from above image), not the popup dev tool.
When I open the popup, I get the following errors:
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
Error handling response: TypeError: Cannot destructure property 'data' of 'undefined' as it is undefined.
I also don't see the service worker actually being registered - it did register before...
Is this a bug with MV3?
My repository (not fully up to date, but can be used to quickly check the above)
Follow these steps:
To send a message from the popup.js to the background service worker, first you need to get current tab id. to get the current tab id do as below:
popup.js
const messageKey = 'key-message-from-popup-to-background';
// Listen to get current tab info from the content_popup
document.addEventListener('_listener_getCurrentTabInfo', function (e) {
const tab = e.detail.response;
// Send a message to the background,js
chrome.tabs.sendMessage(tab.id, messageKey);
});
// Send message to content_popup to get current tab info
document.dispatchEvent(new CustomEvent('_dispatch_getCurrentTabInfo'));
content_popup.js
const config = {
scripts: [
// add ".js" files to web_accessible_resources in manifest.json
"popup/popup.js"
]
};
// Listen to get current tab info from the background.js
document.addEventListener('_dispatch_getCurrentTabInfo', function (e) {
// Key to help
const type = 'get_current_tab_info';
// Send a message to the background.js to get current tab info
chrome.runtime.sendMessage({type: type}, response => {
document.dispatchEvent(new CustomEvent('_listener_getCurrentTabInfo', {detail: {'response': response}}));
});
});
// prepare and add scripts
var scriptList = config['scripts'];
for (var i = 0; i < scriptList.length; i++) {
var s = document.createElement('script');
s.src = chrome.runtime.getURL(scriptList[i]);
s.onload = function () {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
}
background.js
// Get messages
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
// If request related to fetch data from API
if (request.url && request.url !== '') {
// Fetch http request and send back the response
fetch(request.url, request.init).then(function (response) {
return response.text().then(function (text) {
sendResponse([{
body: text,
status: response.status,
statusText: response.statusText,
}, null]);
});
}, function (error) {
sendResponse([null, error]);
});
// If request do not related to fetch data from API
} else {
detectMessageType(request, sender, sendResponse);
}
return true;
});
function detectMessageType(request, sender, sendResponse) {
// Check background request type
if (request && request.type) {
switch (request.type) {
case 'get_current_tab_info': {
getCurrentTabInfo(tab => {
// Send current tab info back to content_popup
sendResponse(tab);
});
break;
}
}
}
});
function getCurrentTabInfo(callback) {
chrome.tabs.query({
active: true,
currentWindow: true
}, function (tab) {
callback(tab[0]);
});
}
Get message in contentScript
chrome.runtime.onMessage.addListener((message, sender, response) => {
const message = message;
// Write your codes
});
This is the project structure (partial)
Project
-- popup
---- popup.js
---- content_popup.js
---- popup.html
---- popup.css
-- content_extension.js
-- background.js
-- manifest.js
Add the following code to the end of the body tag inside the popup.html file
<script src="content_popup.js"></script>
Update the manifest.json file like below
"manifest_version": 3,
.
.
.
"content_scripts": [
{
"js": [
"content_extension.js"
]
}
],
"action": {
"default_icon": ...,
"default_title": ...,
"default_popup": "popup/popup.html"
},
"background": {
"service_worker": "background.js"
},
"web_accessible_resources": [
{
"resources": [
"popup/popup.html",
"popup/popup.js"
]
}
],
...
can you help me again with this problem? I am getting error Uncaught Error: Extension context invalidated. in chrome://extensions.
I am trying to make a timer that whenever you we're idle let's say 5 seconds an alarm will trigger and you need to move your mouse to stop the alarm and I need to sync it with other opened tabs that's why I am using chrome.storage.onChanged.addListener as per the recommendation of wOxxOm thankyou. It works perfectly fine but I am getting this error.
*manifest.json*
{
"manifest_version": 2,
"name": "Toool",
"description": "Description ....",
"version": "1.0.0",
"icons": { "128": "icon_128.png" },
"permissions": ["activeTab", "storage"],
"content_scripts": [
{
"matches": [
"*://*.google.com/*"
],
"css":[
"bootstrap/css/bootstrap-iso.css",
"css/animate.min.css",
"css/all.css",
"css/style.css"
],
"js": [
"js/jquery.min.js",
"js/contentscript.js"
]
}
],
"web_accessible_resources": [
"index.html",
"audio/*",
"webfonts/*"
]
}
*contentscript.js*
// So here is what I've been working so far..
//Instead of popup, I am using this to inject html directly to the DOM.
$('body').prepend('<div class="idletimer-element bootstrap" style="display: block"></div>');
$.get(chrome.extension.getURL('index.html'), function(data) {
$(data).appendTo('.idletimer-element');
});
var audio_url = chrome.runtime.getURL('audio/bells.mp3');
var audio = new Audio(audio_url);
var timerStartIn = 6;
var timer = -1;
var idle = null;
var timeOut = null;
var MouseMoved = 0;
//Initialize the mouse storage value
chrome.storage.local.set({"mouseStorage": MouseMoved}, function() {
console.log('Mouse Moved: ' + MouseMoved);
});
$(document).on('mousemove', function(){
// I used validation here to just execute it when necessary but the problem didn't fix it at all :(
clearTimeout(timeOut);
if(MouseMoved == 0){
MouseMoved = 1;
chrome.storage.local.set({"mouseStorage": MouseMoved}, function() {
console.log('Mouse Moved: ' + MouseMoved);
});
}
timeOut = setTimeout(function(){
MouseMoved = 0;
chrome.storage.local.set({"mouseStorage": MouseMoved}, function() {
console.log('Mouse Moved: ' + MouseMoved);
});
}, 1000);
});
chrome.storage.onChanged.addListener(function(changes, namespace) {
if(changes['mouseStorage']){
IT();
}
});
function IT(){
clearInterval(idle);
timer = -1;
audio.pause(); // Stop playing
audio.currentTime = 0; // Reset time
timerStartIn = 6;
$('.timer').html('User is active');
$('.timer-content i').removeClass('animated shake infinite');
$('.it-status').html('<span> <strong>Reviewer Status: </strong> <span class="active m-r-10">Active</span></span>');
idle = setInterval(function(){
timer++;
//console.log(timer);
timerStartIn--;
$('.timer').html('User is active. <br> Idle starts in ' + timerStartIn + ' second/s');
if(timer >= 5){
//Idle
//console.log('Idle');
$('.timer').html('You are idle for about <br> ' + secondsToHms(timer - 5) +'.');
//console.log(timer);
audio.play();
$('.timer-content i').addClass('animated shake infinite');
$('.it-status').html('<span> <strong>Reviewer Status: </strong> <span class="idle m-r-10">Idle</span></span>');
}
}, 1000);
}
How can I use jQuery if($(this) to update my extensions badge? I've tried the code below and it doesn't work.
jQuery("a[onclick*='ga']").removeAttr('onclick');
if($(this).attr('onclick') == 'ga'){
var add = browser.runtime.sendMessage(message, 'Removed += 1')
}
It should add +1 for each element matching a[onclick='ga'] because that's what it's going to block.
The required scripts for the extension:
manifest.json
{
"manifest_version": 2,
"name": "Anti Click Tracking",
"version": "2.2.4",
"description": "Remove Known HTML <a> and <link> Click Tracking Attributes.",
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"js": [
"/jquery/jquery-3.4.0.min.js", "remove.js"
],
"matches": [
"<all_urls>"
]
}
],
"permissions": ["<all_urls>", "webRequest", "webRequestBlocking"],
}
background.js
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if (!message) return
if (message <= 0) return
const text = message.toString()
chrome.tabs.get(sender.tab.id, function(tab) {
// Pre-rendered tab has been nuked
if (chrome.runtime.lastError) return
// Tab is visible
if (tab.index >= 0) {
chrome.browserAction.setBadgeText({
tabId: tab.id,
text: text
})
return
}
// Invisible pre-rendered tab
const tabId = sender.tab.id
chrome.webNavigation.onCommitted.addListener(function update(details) {
if (details.tabId == tabId) {
chrome.browserAction.setBadgeText({
tabId: tabId,
text: text
})
chrome.webNavigation.onCommitted.removeListener(update)
}
})
})
})
remove.js
// Remove ping attribute from <a> (somewhat based on PingBlock)
window.onload = function() {
let Removed = 0
const anchorElements = document.getElementsByTagName('A')
for (element of anchorElements) {
if (!element.getAttribute('ping')) continue
console.log("Removed ping: " + element.getAttribute('ping'))
element.removeAttribute('ping')
Removed += 1
chrome.extension.sendMessage(Removed)
}
}
// Remove Link Tracking
link();
function link(){
jQuery("a[onclick*='ga']").removeAttr('onclick');
jQuery("a[onclick*='_gaq.push']").removeAttr('onclick');
jQuery("link[rel*='pingback']").removeAttr('rel');
}
All this code together only updates the badge when the ping attribute is blocked. I need it to also update the badge when jQuery removes the selected attributes. Is it possible to use the Removed += 1 like I did under the first part for jQuery or will I have to change it to something else?
I'm creating a Chrome extension that should interact with the user selection in the Microsoft Word Online documents - add new highlighting instead of the natural selection highlighting and then remove it.
The problem is that I'm not able to get the user selection: the response for the window.getSelection() returns the result like the selection is empty.
Here are files from my extension:
manifest.json
{
"manifest_version": 2,
"name": "The extension name",
"version": "1.0",
"description": "This extension description",
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"icons": {
"128": "icon.png"
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content_script.js"],
"run_at": "document_end",
"all_frames": true
}],
"permissions": ["activeTab"]
}
popup.html
<!doctype html>
<html>
<head>
<script src="popup.js"></script>
</head>
<body>
<div id="wrapper">
<form id="settings" name="settings">
<div id="apply" class="form-row">
<input type="submit" name="apply" value="Apply"/>
</div>
</form>
</div>
</body>
</html>
popup.js
document.addEventListener('DOMContentLoaded', function() {
document.getElementById("settings").addEventListener("submit", function (e) {
e.preventDefault();
chrome.tabs.executeScript(null, {file: 'toolbar.js'}, function() {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {});
});
);
}, false);
});
toolbar.js
function showToolbar() {
var dom_body = document.getElementsByTagName('body')[0];
var tb_wrapper = document.createElement('div');
tb_wrapper.id = "toolbar_wrapper";
var tb_toolbar_play = document.createElement('button');
tb_toolbar_play.id = "toolbar_play";
tb_toolbar_play.title = "Play";
tb_toolbar_play.value = "Play";
tb_wrapper.appendChild(tb_toolbar_play);
dom_body.appendChild(tb_wrapper);
}
showToolbar();
content_script.js
function playButtonOnClickEventListener(request, sender, sendResponse) {
var toolbar = document.getElementById("toolbar_wrapper");
if (toolbar !== null) {
var toolbar_play_button = document.getElementById("toolbar_play");
toolbar_play_button.addEventListener("click", function (e) {
var selection = window.getSelection();
console.log(selection);
});
}
sendResponse({data: "response", success: true});
}
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
playButtonOnClickEventListener(request, sender, sendResponse);
});
So, what I want to see in the Chrome Developer tools after the console.log(selection) executes:
What I actually get:
P.S. The extension works perfectly with Google Docs.
I'm not sure how the Word Online editor is built or why you can't use window.getSelection(), but one thing I noticed immediately is that when you select text in it, the editor gives that text a Selected class. So maybe you could take advantage of that somehow? Override the style of the relevant elements?
Possibly you could use a MutationObserver and look for this class name being added to elements. You would have to do some experimenting to see if this works for your needs, and obviously if Microsoft change the class name or behaviour, your extension breaks.
Try selecting some text and putting
document.getElementsByClassName("Selected")[0].innerHTML
in the console.
Here's the solution of the issue. Hope it can be useful for somebody once.
Currently I see the whole picture differently, so I changed the number of files and its content.
manifest.json
{
"manifest_version": 2,
"version": "1.1.1",
"name": "Test name",
"description": "Test description",
"browser_action": {
"default_icon": "icon.png"
},
"icons": {
"128": "icon.png"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_script.js"],
"run_at": "document_end",
"all_frames": true
}
],
"permissions": ["activeTab"]
}
content_script.js
//Word Online pages contain several different iframes, so, in order
//to find the one where the text that we are going to work with
//is placed, we have to use the Chrome Runtime API and its
//"sendMessage" method that works in every frame.
//https://developer.chrome.com/apps/runtime#method-sendMessage
chrome.runtime.sendMessage("", function() {
if (checkFrameLocation()) {
showToolbar();
}
});
function checkFrameLocation() {
//So as I work with documents in OneDrive (onedrive.live.com)
//and in Sharepoint (<USER_NAME>-my.shrepoint.com), I found out
//that I need iframes with these URLs only.
var trusted_urls = [
"https://euc-word-edit.officeapps.live.com",
"https://word-edit.officeapps.live.com",
"nl1-word-edit.officeapps.live.com"
];
var result = false;
for (var i = 0; i < trusted_urls.length; i++) {
if (window.location.href.indexOf(trusted_urls[i]) > -1) {
result = true;
}
}
return result;
}
function showToolbar() {
var dom_body = document.body;
var tb_wrapper = document.createElement('div');
tb_wrapper.id = "toolbar_wrapper";
tb_wrapper.style.position = "absolute";
tb_wrapper.style.top = "200px";
tb_wrapper.style.left = "200px";
tb_wrapper.style.backgroundColor = "#ffffff";
tb_wrapper.style.height = "30px";
tb_wrapper.style.width = "50px";
var tb_play = document.createElement('button');
tb_play.id = "toolbar_play_button";
tb_play.title = "Play";
tb_play.innerHTML = "►";
tb_play.style.height = "100%";
tb_play.style.width = "100%";
tb_play.style.lineHeight = "12px";
tb_play.addEventListener("click", function() {
playButtonOnClickEventListener();
});
tb_wrapper.appendChild(tb_play);
dom_body.appendChild(tb_wrapper);
}
function playButtonOnClickEventListener() {
//Now we can use the window selection object
var window_selection = window.getSelection();
console.log(window_selection);
//Also, Word Online adds special class name to every selected element
//in the document. So, this is another way to get the selected text.
var elements_selection = document.getElementsByClassName("Selected");
console.log(elements_selection);
}
And here's the proof screenshot:
There are results of accessing the selected text in the Word Online document