Skip to content

Commit

Permalink
fix: restore compatibility with Safari 18
Browse files Browse the repository at this point in the history
  • Loading branch information
dessant committed Sep 26, 2024
1 parent f46b754 commit 2de3409
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 41 deletions.
29 changes: 27 additions & 2 deletions src/background/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import {
getNetRequestRuleIds,
getAppTheme,
showPage,
getOpenerTabId
getOpenerTabId,
addTabRevision
} from 'utils/app';
import {
getText,
Expand Down Expand Up @@ -1614,6 +1615,13 @@ async function processMessage(request, sender) {
}

return executeScript(params);
} else if (request.id === 'addStorageItem') {
const storageId = await registry.addStorageItem(
request.data,
request.params
);

return Promise.resolve(storageId);
}
}

Expand All @@ -1623,7 +1631,11 @@ async function processConnection(port) {

await setMessagePort(id, port);

port.postMessage({transfer: {type: 'connection', complete: true, id}});
// Safari 18: messages sent from the runtime.onConnect event handler
// are no longer delivered, the message needs to be delayed.
self.setTimeout(function () {
port.postMessage({transfer: {type: 'connection', complete: true, id}});
}, 100);
}
}

Expand Down Expand Up @@ -1681,6 +1693,10 @@ async function onStartup() {
await setup({event: 'startup'});
}

async function onTabReplaced(addedTabId, removedTabId) {
await addTabRevision({addedTabId, removedTabId});
}

function addContextMenuListener() {
if (browser.contextMenus) {
browser.contextMenus.onClicked.addListener(onContextMenuItemClick);
Expand Down Expand Up @@ -1719,6 +1735,14 @@ function addStartupListener() {
browser.runtime.onStartup.addListener(onStartup);
}

function addTabReplacedListener() {
// Safari 18: tabId changes when an extension page is redirected
// to a website, changes are saved to assign tasks to the correct tab.
if (['safari'].includes(targetEnv)) {
browser.tabs.onReplaced.addListener(onTabReplaced);
}
}

async function setupUI() {
const items = [setBrowserAction];

Expand Down Expand Up @@ -1775,6 +1799,7 @@ function init() {
addAlarmListener();
addInstallListener();
addStartupListener();
addTabReplacedListener();

setup();
}
Expand Down
26 changes: 26 additions & 0 deletions src/capture/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import Cropper from 'cropperjs';
import {App, Button, IconButton, Snackbar} from 'vueton';
import {validateId} from 'utils/app';
import {getText} from 'utils/common';
export default {
Expand Down Expand Up @@ -60,6 +61,31 @@ export default {
setup: async function () {
if (this.$env.isFirefox) {
browser.runtime.onMessage.addListener(this.onMessage);
browser.runtime.sendMessage({
id: 'routeMessage',
setSenderFrameId: true,
messageFrameId: 0,
message: {id: 'saveFrameId'}
});
} else if (this.$env.isSafari) {
window.addEventListener('message', async ev => {
const messageId = ev.data;
if (validateId(messageId)) {
const message = await browser.runtime.sendMessage({
id: 'storageRequest',
asyncResponse: true,
saveReceipt: true,
storageId: messageId
});
if (message) {
this.onMessage(message);
}
}
});
browser.runtime.sendMessage({
id: 'routeMessage',
setSenderFrameId: true,
Expand Down
28 changes: 27 additions & 1 deletion src/confirm/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ import {
isPreviewImageValid,
sendLargeMessage,
processLargeMessage,
getAppTheme
getAppTheme,
validateId
} from 'utils/app';
import {getText} from 'utils/common';
Expand Down Expand Up @@ -363,6 +364,31 @@ export default {
init: async function () {
if (this.$env.isFirefox) {
browser.runtime.onMessage.addListener(this.onMessage);
browser.runtime.sendMessage({
id: 'routeMessage',
setSenderFrameId: true,
messageFrameId: 0,
message: {id: 'saveFrameId'}
});
} else if (this.$env.isSafari) {
window.addEventListener('message', async ev => {
const messageId = ev.data;
if (validateId(messageId)) {
const message = await browser.runtime.sendMessage({
id: 'storageRequest',
asyncResponse: true,
saveReceipt: true,
storageId: messageId
});
if (message) {
this.processMessage(message);
}
}
});
browser.runtime.sendMessage({
id: 'routeMessage',
setSenderFrameId: true,
Expand Down
28 changes: 22 additions & 6 deletions src/content/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,27 @@ async function sendQueuedMessage() {

async function messageView(message) {
if (contentStorage.viewFrameId) {
await browser.runtime.sendMessage({
id: 'routeMessage',
messageFrameId: contentStorage.viewFrameId,
message
});
if (targetEnv === 'safari') {
const messageId = await browser.runtime.sendMessage({
id: 'addStorageItem',
data: message,
params: {
receipts: {expected: 1, received: 0},
expiryTime: 1.0,
area: 'memory'
}
});

contentStorage.viewFrame.contentWindow.postMessage(messageId, {
targetOrigin: browser.runtime.getURL('/')
});
} else {
await browser.runtime.sendMessage({
id: 'routeMessage',
messageFrameId: contentStorage.viewFrameId,
message
});
}
} else if (contentStorage.viewMessagePort) {
await sendLargeMessage({
target: 'port',
Expand Down Expand Up @@ -167,7 +183,7 @@ function main() {
addViewFrame();

browser.runtime.onMessage.addListener(onMessage);
if (targetEnv !== 'firefox') {
if (!['firefox', 'safari'].includes(targetEnv)) {
browser.runtime.onConnect.addListener(onConnect);
}
}
Expand Down
37 changes: 37 additions & 0 deletions src/select/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<script>
import {App, IconButton, Snackbar} from 'vueton';
import {validateId} from 'utils/app';
import {getText} from 'utils/common';
export default {
Expand All @@ -40,6 +41,42 @@ export default {
setup: async function () {
if (this.$env.isFirefox) {
browser.runtime.onMessage.addListener(this.onMessage);
browser.runtime.sendMessage({
id: 'routeMessage',
setSenderFrameId: true,
messageFrameId: 0,
message: {id: 'saveFrameId'}
});
} else if (this.$env.isSafari) {
// Safari 18: extension pages loaded in iframes can no longer connect
// to the content script of the top-level document with tabs.connect,
// event handlers registered with runtime.onConnect
// in the content script are no longer called.
// Messages sent from the background script using tabs.sendMessage
// are still not received by the extension page in Safari, so we must
// notify the extension page from the content script using
// frame.contentWindow.postMessage to retrieve its message
// from the background script.
window.addEventListener('message', async ev => {
const messageId = ev.data;
if (validateId(messageId)) {
const message = await browser.runtime.sendMessage({
id: 'storageRequest',
asyncResponse: true,
saveReceipt: true,
storageId: messageId
});
if (message) {
this.onMessage(message);
}
}
});
browser.runtime.sendMessage({
id: 'routeMessage',
setSenderFrameId: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ async function upgrade() {
const changes = {
platformInfo: null,
menuChangeEvent: 0,
privateMenuChangeEvent: 0
privateMenuChangeEvent: 0,
tabRevisions: []
};

changes.storageVersion = revision;
Expand Down
54 changes: 49 additions & 5 deletions src/utils/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
isAndroid,
getDarkColorSchemeQuery,
isValidTab,
getRandomInt
getRandomInt,
requestLock
} from 'utils/common';
import {
targetEnv,
Expand Down Expand Up @@ -1821,9 +1822,13 @@ async function sendLargeMessage({
messagePort = port;
messagePort.onMessage.addListener(messageCallback);

messagePort.postMessage({
transfer: {type: 'connection', complete: true, id: transferId}
});
// Safari 18: messages sent from the runtime.onConnect event handler
// are no longer delivered, the message needs to be delayed.
self.setTimeout(function () {
messagePort.postMessage({
transfer: {type: 'connection', complete: true, id: transferId}
});
}, 100);
}
};

Expand Down Expand Up @@ -2100,6 +2105,43 @@ async function requestClipboardReadPermission() {
return browser.permissions.request({permissions: ['clipboardRead']});
}

async function addTabRevision({addedTabId, removedTabId} = {}) {
return requestLock('tab_revisions', async () => {
const {tabRevisions} = await storage.get('tabRevisions', {area: 'session'});

let entryFound = false;

for (const tabIds of tabRevisions) {
if (tabIds.includes(removedTabId)) {
tabIds.push(addedTabId);

entryFound = true;
break;
}
}

if (!entryFound) {
tabRevisions.push([removedTabId, addedTabId]);
}

await storage.set({tabRevisions}, {area: 'session'});
});
}

async function getTabRevisions(tabId) {
return requestLock('tab_revisions', async () => {
const {tabRevisions} = await storage.get('tabRevisions', {area: 'session'});

for (const tabIds of tabRevisions) {
if (tabIds.includes(tabId)) {
return tabIds;
}
}

return null;
});
}

export {
getEnabledEngines,
getSupportedEngines,
Expand Down Expand Up @@ -2190,5 +2232,7 @@ export {
getMaxExtensionMessageSize,
getMaxDataUrlSize,
hasClipboardReadPermission,
requestClipboardReadPermission
requestClipboardReadPermission,
addTabRevision,
getTabRevisions
};
10 changes: 10 additions & 0 deletions src/utils/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,15 @@ function runOnce(name, func) {
}
}

async function requestLock(name, func, {timeout = 60000} = {}) {
const params = [name];
if (timeout) {
params.push({signal: AbortSignal.timeout(timeout)});
}

return navigator.locks.request(...params, func);
}

function sleep(ms) {
return new Promise(resolve => self.setTimeout(resolve, ms));
}
Expand Down Expand Up @@ -871,5 +880,6 @@ export {
filenameToFileExt,
getStore,
runOnce,
requestLock,
sleep
};
Loading

0 comments on commit 2de3409

Please sign in to comment.