I'm currently working on a Chrome extension where I need to process multiple HTML contents using an offscreen document in the service worker. The aim is to ensure that each HTML content is parsed and processed correctly. However, I am encountering an issue where the chrome.runtime.getContexts method returns an empty array, even though the offscreen document appears to be created and functioning properly.
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const OFFSCREEN_DOCUMENT_PATH = '/offscreen.html';
let creating; // A global promise to avoid concurrency issues
async function setupOffscreenDocument(path) {
const offscreenUrl = chrome.runtime.getURL(path);
const existingContexts = await chrome.runtime.getContexts({
contextTypes: ['OFFSCREEN_DOCUMENT'],
documentUrls: [offscreenUrl]
});
console.log("existingContexts", existingContexts);
if (existingContexts.length > 0) {
console.log('Offscreen document already exists.');
return;
}
if (creating) {
await creating;
} else {
console.log("creating offscreen document");
creating = chrome.offscreen.createDocument({
url: path,
reasons: [chrome.offscreen.Reason.DOM_PARSER],
justification: 'Parse DOM'
});
await creating;
creating = null;
let contexts = await chrome.runtime.getContexts({
contextTypes: ['OFFSCREEN_DOCUMENT'],
documentUrls: [offscreenUrl]
});
console.log("contexts after create:", contexts);
}
}
chrome.action.onClicked.addListener(async () => {
await setupOffscreenDocument(OFFSCREEN_DOCUMENT_PATH);
// Array of HTML contents
const htmlContents = [
'<html><head></head><body><h1>Hello World 1</h1></body></html>',
// '<html><head></head><body><h1>Hello World 2</h1></body></html>',
// '<html><head></head><body><h1>Hello World 3</h1></body></html>',
// '<html><head></head><body><h1>Hello World 4</h1></body></html>',
// '<html><head></head><body><h1>Hello World 5</h1></body></html>'
];
// Send each HTML content to offscreen document
const processingPromises = [];
for (const htmlContent of htmlContents) {
processingPromises.push(sendMessageToOffscreenDocument('add-exclamationmarks-to-headings', htmlContent)
.then(() => console.log("parsed htmlcontent:", htmlContent))
.catch(error => console.error('Failed to process HTML content:', error)));
}
console.log('All HTML contents processed.');
Promise.all(processingPromises).then(result => {
console.log(result);
}).catch(error => console.log(`Error in promises ${error}`));
await closeOffscreenDocument();
});
async function sendMessageToOffscreenDocument(type, data) {
const messageId = generateUUID();
return new Promise((resolve, reject) => {
const messageHandler = (message, sender, sendResponse) => {
if (message.type === 'add-exclamationmarks-result' && message.id === messageId) {
chrome.runtime.onMessage.removeListener(messageHandler);
resolve(message.result);
}
};
chrome.runtime.onMessage.addListener(messageHandler);
chrome.runtime.sendMessage({
type,
target: 'offscreen',
data,
id: messageId
});
console.log("sended message to offscreen: ",type, data, messageId)
});
}
chrome.runtime.onMessage.addListener(handleMessages);
async function handleMessages(message) {
if (message.target !== 'background') {
return;
}
switch (message.type) {
case 'add-exclamationmarks-result':
handleAddExclamationMarkResult(message.data);
break;
default:
console.warn(`Unexpected message type received: '${message.type}'.`);
}
}
async function handleAddExclamationMarkResult(dom) {
console.log('Received dom', dom);
}
async function closeOffscreenDocument() {
if (!(await hasOffscreenDocument())) {
console.log("no offscreen document to close ")
return;
}
console.log("closing offscreen document")
await chrome.offscreen.closeDocument();
console.log("closed offscreen document");
}
async function hasOffscreenDocument() {
console.log("checking offscreen document")
if ('getContexts' in chrome.runtime) {
const contexts = await chrome.runtime.getContexts({
contextTypes: ['OFFSCREEN_DOCUMENT'],
documentUrls: [OFFSCREEN_DOCUMENT_PATH]
});
console.log("contexts", contexts)
return Boolean(contexts.length);
} else {
const matchedClients = await clients.matchAll();
console.log("matchedClients", matchedClients)
return matchedClients.some(client => client.url.includes(chrome.runtime.id));
}
}
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Registering this listener when the script is first executed ensures that the
// offscreen document will be able to receive messages when the promise returned
// by `offscreen.createDocument()` resolves.
chrome.runtime.onMessage.addListener(handleMessages);
// This function performs basic filtering and error checking on messages before
// dispatching the message to a more specific message handler.
async function handleMessages(message) {
// Return early if this message isn't meant for the offscreen document.
if (message.target !== 'offscreen') {
return false;
}
console.log("received message in offscreen:",message)
// Dispatch the message to an appropriate handler.
switch (message.type) {
case 'add-exclamationmarks-to-headings':
addExclamationMarksToHeadings(message.data);
break;
default:
console.warn(`Unexpected message type received: '${message.type}'.`);
return false;
}
}
function addExclamationMarksToHeadings(htmlString) {
const parser = new DOMParser();
const document = parser.parseFromString(htmlString, 'text/html');
document
.querySelectorAll('h1')
.forEach((heading) => (heading.textContent = heading.textContent + '!!!'));
sendToBackground(
'add-exclamationmarks-result',
document.documentElement.outerHTML
);
}
function sendToBackground(type, data) {
chrome.runtime.sendMessage({
type,
target: 'background',
data
});
console.log("sended message to background: ",type, data)
}