I'm wondering if it's possible to allow the user to upload an image to the extension and have that image display as a background image.
Let the user select a file, with <input type="file">
Turn the file into a data URL
Save the data URL with chrome.storage.local.set
Use a content script to replace the background image in web pages
Potential Problem: https://developer.chrome.com/docs/extensions/mv3/declare_permissions/#unlimitedStorage
Note: This permission applies only to Web SQL Database and application
cache (see issue 58985). Also, it doesn't currently work with wildcard
subdomains such as http://*.example.com.
Proof of concept:
https://github.com/GrippenDynamik/Set_Background_Image
manifest.json
{
"manifest_version": 3,
"name": "Set Background Image",
"version": "1.0",
"action": {
"default_title": "Set Background Image"
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["/js/content_script.js"]
}
],
"permissions": [
"storage"
]
}
background.js
async function action_onClicked(tab, onClickData) {
chrome.tabs.create({
active: true,
url: chrome.runtime.getURL("/html/file_picker.html"),
});
}
chrome.action.onClicked.addListener(action_onClicked);
/html/file_picker.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<label for="file_picker">Choose an image to upload</label><br>
<input type="file" id="file_picker" accept="image/*"><br>
<br>
<button type="button" id="button_clear">Clear background image</button>
<br>
<img id="image"><br>
<script type="application/javascript" src="/js/file_picker.js"></script>
</body>
</html>
/js/file_picker.js
function storage_onChanged(changes, areaName) {
let settings = {};
if (areaName == "local") {
if (changes.image_data_url) {
settings.image_data_url = changes.image_data_url.newValue ?? "";
}
if (changes.image_filename) {
settings.image_filename = changes.image_filename.newValue ?? "none";
}
}
set_image(settings);
}
function set_image(settings) {
let image = document.getElementById("image");
if (settings.image_data_url !== undefined) { image.src = settings.image_data_url; }
if (settings.image_filename !== undefined) { image.alt = settings.image_filename; }
}
function remove_image() {
chrome.storage.local.remove(["image_data_url", "image_filename"]);
}
function store_image() {
if (this.files.length > 0) {
const reader = new FileReader();
reader.addEventListener("load", () => {
chrome.storage.local.set({"image_data_url": reader.result, "image_filename": this.files[0].name});
});
reader.readAsDataURL(this.files[0]);
}
}
// Initialization
chrome.storage.local.get(["image_data_url", "image_filename"])
.then(items => {
set_image({image_data_url: items.image_data_url ?? "", image_filename: items.image_filename ?? "none"});
}
);
document.getElementById("file_picker").addEventListener("change", store_image);
document.getElementById("button_clear").addEventListener("click", remove_image);
chrome.storage.onChanged.addListener(storage_onChanged);
/js/content_script.js
function storage_onChanged(changes, areaName) {
if (areaName == "local" && changes.image_data_url) {
set_background_image(changes.image_data_url.newValue, true);
}
}
function set_background_image(data_url, changed) {
if (data_url) {
// https://www.w3schools.com/jsref/prop_style_backgroundimage.asp
document.body.style.backgroundImage = "url('" + data_url + "')";
}
else if (changed) {
document.body.style.backgroundImage = "initial";
}
else {
console.log("You haven't 'uploaded' an image yet. Please click the extension action.");
}
}
// Initialization
chrome.storage.local.get("image_data_url")
.then(items => set_background_image(items.image_data_url, false));
chrome.storage.onChanged.addListener(storage_onChanged);
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");
}
}
I am making a chrome extension that will automatically sign up for a Roblox account. (yes i still manually do captcha)
I am having some trouble with creating a new tab, and injecting a content script.
It seems that the script wont inject at all because i tried using an alert() message in the script and did not show up at all.
Here is my code if you need it
manifest.json:
{
"manifest_version": 2,
"name": "Roblox Alt Tools",
"description": "all the tools necicary for roblox alts",
"version": "1.0",
"permissions": ["tabs", "<all_urls>"],
"icons": {
"16": "/images/icon16.png",
"48": "/images/icon48.png",
"128": "/images/icon128.png"
},
"browser_action": {
"default_icon": {
"16": "/images/icon16.png",
"48": "/images/icon48.png",
"128": "/images/icon128.png"
},
"default_popup": "popup.html"
}
}
popup.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Roblox Alt Tools</title>
<style>
body {
min-width: 250px;
max-width: 100%;
position: relative;
vertical-align:middle;
}
</style>
<script src="popup.js"></script>
</head>
<body>
<center><h1>Roblox Alt Tools</h1>
<h3>the ultimate alt manager for Roblox</h3>
<br>
<button class="generate-acc" type="button">generate</button>
</center>
</body>
</html>
popup.js
if (document.readyState == 'loading') {
document.addEventListener('DOMContentLoaded', ready)
} else {
ready()
}
function delay(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
async function generate() {
chrome.tabs.create({url: 'https://www.roblox.com'});
await delay(3000)
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.executeScript(tabs[0].id, {file: "content_script.js"});
});
}
function ready() {
var btn = document.getElementsByClassName('generate-acc')[0];
btn.addEventListener('click', generate)
}
content_script.js
var password = "sub2piggygaming"
function randomstr(length) {
var result = '';
var characters = 'abcdefghijklmnopqrstuvwxyz';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() *
charactersLength));
}
return result;
}
async function signup() {
var signupbtn = document.getElementById("signup-button")
var usernamebtn = document.getElementById("signup-username")
var passwordbtn = document.getElementById("signup-password")
var monthdrop = document.getElementById("MonthDropdown")
var daydrop = document.getElementById("DayDropdown")
var yeardrop = document.getElementById("YearDropdown")
const keyboardEventInit = {bubbles:false, cancelable:false, composed:false, key:'', code:'', location:0};
usernamebtn.dispatchEvent(new KeyboardEvent("keydown", keyboardEventInit));
usernamebtn.value = await randomstr(10);
usernamebtn.dispatchEvent(new KeyboardEvent("keyup", keyboardEventInit));
usernamebtn.dispatchEvent(new Event('change', {bubbles: true}));
passwordbtn.dispatchEvent(new KeyboardEvent("keydown", keyboardEventInit));
passwordbtn.value = password;
passwordbtn.dispatchEvent(new KeyboardEvent("keyup", keyboardEventInit));
passwordbtn.dispatchEvent(new Event('change', {bubbles: true}));
monthdrop.dispatchEvent(new KeyboardEvent("keydown", keyboardEventInit));
monthdrop.value = 'Jan';
monthdrop.dispatchEvent(new KeyboardEvent("keyup", keyboardEventInit));
monthdrop.dispatchEvent(new Event('change', {bubbles: true}));
daydrop.dispatchEvent(new KeyboardEvent("keydown", keyboardEventInit));
daydrop.value = '01';
daydrop.dispatchEvent(new KeyboardEvent("keyup", keyboardEventInit));
daydrop.dispatchEvent(new Event('change', {bubbles: true}));
yeardrop.dispatchEvent(new KeyboardEvent("keydown", keyboardEventInit));
yeardrop.value = '1978';
yeardrop.dispatchEvent(new KeyboardEvent("keyup", keyboardEventInit));
yeardrop.dispatchEvent(new Event('change', {bubbles: true}));
await new Promise(resolve => setTimeout(resolve, 1000));
signupbtn.click();
}
if (document.readyState == 'loading') {
document.addEventListener('DOMContentLoaded', ready);
} else {
signup();
}
I think the problem is the content script is executing before the tab loads.
I tried using setTimeout(), and also a Promise delay, still does not work.
Yes, the content script works in an already-loaded tab.
And yes, I looked on google, no results that work. is this even possible? do I gotta abandon my project?
thank you.
I can't figure out how to inject HTML and javascript onto a webpage with Firefox extensions.
This works with Chrome extension but does NOT work with Firefox. Notice I use chrome.extension.getURL which pulls in HTML and Javascript.
Here is my manifest - note I dont even use the background stuff
{
"background": {
"scripts": [ "js/background.js", "js/jquery.js"]
},
"content_scripts": [ {
"js": [ "js/jquery.js", "js/chart-min.js", "js/chart-colors.js", "js/jquery-ui.min.js", "js/profiles/my_request.js", "js/options.js", "js/profiles/projects_builder.js", "js/profiles/board_builder.js", "js/profiles/my_api.js", "js/profiles/user_board_builder.js", "js/profiles/user_board.js","js/profiles/default_labels.js", "js/profiles/default_lists.js", "js/profiles/master_board.js", "js/profiles/projects.js", "js/profiles/estimates.js", "js/profiles/new_todo_label.js","js/profiles/reports_dashboard.js", "js/profiles/mutation_observer.js", "js/profiles/completion_chart.js", "js/profiles/cumulative_flow_chart.js" ],
"matches": [ "https://asana.com/*" ],
"all_frames": true
}],
"permissions":[ "identity", "cookies", "storage", "activeTab", "https://asana.com/*"],
"name": "Boards here",
"short_name" : "boards",
"version": "3.1.9.5",
"manifest_version": 2,
"icons" : { "48": "images/logo_thicker.png"},
"description": "html for website",
"browser_action":{
"default_icon":"images/logo_thicker.png",
"default_popup":"html/popup.html"
},
"web_accessible_resources": [ "images/*", "html/*" ]
}
Example for default lists -- default_lists.js utilizing my_request which is just a jquery ajax wrapper
DefaultLists = (function() {
function DefaultLists() {
if (window.location.href.includes('#default_lists')) {
this.show_form()
}
DefaultLists.prototype.show_form = function {
my_request.ajax({
url: chrome.extension.getURL("html/manage_default_lists.html"),
type: 'get',
success: function (data) {
$('.panel.panel--project').remove()
$('.panel.panel--perma').html(data)
}
});
};
}
return DefaultLists;
})();
window.default_lists = new DefaultLists();
So now manage_default_lists.html looks something like
<section style="position:relative;top:-50px;" class="message-board__content">
<bc-infinite-page page="1" reached-infinity="" direction="down" trigger="bottom">
<table class="my_labels" data-infinite-page-container="">
<tbody>
<tr id="loading_lists" >
<td>
<span style="margin-left:280px;color:grey">Loading...</span>
</td>
</tr>
<tr id="create_row" style="display:none">
<td>
<span class="">
<input id="new_label_name" placeholder="List name" type="text" style="width:180px;font-size:15px;margin-left:42px;padding:5px;border-radius: 0.4rem;border: 1px solid #bfbfbf;" value="">
<a style="margin-left:10px;float:right;margin-right:80px" class="cancel_new btn--small btn small" href="#">cancel</a>
<input id="create_label" style="float:right;" type="submit" value="save" class="btn btn--small btn--primary primary small" data-behavior="loading_on_submit primary_submit" data-loading-text="saving…">
</span>
</td>
</tr>
</tbody>
</table>
</bc-infinite-page>
</section>
</article>
<script>
$('#cancel_delete_label_button').on('click', function(event){
$('#delete_label_modal').hide()
});
$('#cancel_force_lists_button').on('click', function(event){
$('#force_lists_modal').hide()
});
$(document).off( "click", '.edit_label').on('click', '.edit_label', function(event) {
td = $(this).parents('td')
td.find('.show_row').hide()
td.find('.edit_row').show()
event.preventDefault()
});
$(document).off( "click", '.cancel_edit').on('click', '.cancel_edit', function(event) {
td = $(this).parents('td')
td.find('.show_row').show()
td.find('.edit_row').hide()
event.preventDefault()
});
$(document).off( "click", '.cancel_new').on('click', '.cancel_new', function(event) {
// console.log('cancel')
$('#create_row').hide()
event.preventDefault()
});
$(document).off( "click", '#new_label_button').on('click', '#new_label_button', function(event) {
$('#create_row').show()
$('#new_label_name').val('')
event.preventDefault()
});
$(document).off( "click", '#labels_article').on('click', '#labels_article', function(event) {
// console.log(event.target.className)
if (event.target.className != 'color-editor-bg'){
$('.label-colors').hide();
}
});
</script>
You're adding this HTML into the web page using your content script and web_accessible_resources. It's not related to the extension's CSP that forbids inline scripts in extension pages like the browser_action popup or the options page. In your case the CSP of the page applies to the stuff you add in it. The page may forbid inline scripts easily, sites often do that.
You can either rewrite the Content-Security-Policy HTTP header of the page using webRequest API or rework the code a bit, the latter being a better solution not only because it's more focused but also because the result of rewriting an HTTP header is random when several extensions are doing it in the same HTTP request.
So let's rework the code:
store the scripts separately
fetch and inject via browser.tabs.executeScript
run the injected code when we want it
The important change of behavior is that the code will be running in the content script's context, using content script's jQuery and variables. Previously your code was running in the page context so it was using jQuery and other variables of the page.
I'm using Mozilla's browser WebExtension namespace polyfill and async/await syntax.
manifest.json:
"background": {
"scripts": [
"browser-polyfill.min.js",
"background.js"
]
},
"content_scripts": [{
"js": [
"browser-polyfill.min.js",
"content.js"
],
"matches": ["........."]
}],
directory structure:
html/manage_default_lists.html
html/manage_default_lists.js
Since executeScript can only be used in an extension page, not in a content script, we'll send a message to the background script with an instruction.
content.js:
async function getResource(name) {
const htmlUrl = browser.runtime.getURL(`html/${name}.html`);
const [html, scriptId] = await Promise.all([
fetch(htmlUrl).then(r => r.text()),
browser.runtime.sendMessage({
cmd: 'executeScript',
path: `html/${name}.js`,
}),
]);
const js = window[scriptId];
delete window[scriptId];
return {html, js};
}
DefaultLists.prototype.show_form = async () => {
const {html, js} = await getResource('manage_default_lists');
$('.panel.panel--project').remove()
$('.panel.panel--perma').html(html);
js();
};
background.js:
browser.runtime.onMessage.addListener(async (message, sender) => {
if (!message) return;
switch (message.cmd) {
case 'executeScript': {
const js = await (await fetch(browser.runtime.getURL(message.path))).text();
const id = `js.${performance.now()}${Math.random()}`;
await browser.tabs.executeScript(sender.tab.id, {
// 0 at the end is to prevent executeScript from additionally returning
// the function's code as a string, which we don't need
code: `window["${id}"] = () => { ${js} };0`,
frameId: sender.frameId,
matchAboutBlank: true,
runAt: 'document_start',
});
return id;
}
}
});
When I click on the toolbar icon from my chrome extension, it brings up the default popup.html along with popup.js. From that popup.js, I want to send a message to the current active tab. I can't figure out how to get the active tab.
My code in the popup.js looks like this:
window.onload = function() {
console.log(`popup.js:window.onload`);
const timeNow = document.getElementById("timeNowId");
timeNow.innerText = new Date().toLocaleTimeString();
var resultsButton = document.getElementById("buttonId");
resultsButton.onclick = () => {
chrome.runtime.sendMessage(
"FromPopupToBackgroundToContent:" + timeNow.innerText,
function(response) {
response === undefined
? alert("popup.js:return from sendMessage:undefined")
: alert("popup.js:return from sendMessage:", response);
}
);
};
};
The full, non-working example is in this github repo at this branch linked to.
https://github.com/pkellner/chrome-extension-message-popup-to-content/tree/feature-popup-to-content-directly-getcurrent
Example:
popup.js
function popup() {
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
var activeTab = tabs[0];
chrome.tabs.sendMessage(activeTab.id, {"message": "start"});
});
}
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("button1").addEventListener("click", popup);
});
content.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if( request.message === "start" ) {
start();
}
}
);
function start(){
alert("started");
}
popup.html
<!DOCTYPE html>
<html>
<head></head>
<script src="popup.js"></script>
<body>
<input id="button1" type=button value=clickme>
</body>
</html>
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