Skip to content

Conversation

@rebloor
Copy link
Collaborator

@rebloor rebloor commented Nov 11, 2025

Description

Updates the borderify and beastify examples to use Manifest V3.

Motivation

These examples are used in the MDN getting started "your first extension" and "your second extension" tutorials. This change enables the getting-started guides up to be updated and brought in line with best practices for extension development using Manifest V3.

Related issues and pull requests

Related changes to MDN content in PR TBC.

@rebloor rebloor requested a review from dotproto November 11, 2025 23:11

"browser_specific_settings": {
"gecko": {
"id": "[email protected]",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the prevailing pattern in for the examples in this repo is <example-name>@mozilla.org where example-name is a kebab case identifier that resembles the example's parent directory. This pattern has the benefit of being reserved on AMO, so developers cannot submit add-ons with these IDs while testing.

Suggested change
"id": "borderify@example.com",
"id": "borderify@mozilla.org",

"browser_action": {
"browser_specific_settings": {
"gecko": {
"id": "[email protected]",
Copy link
Contributor

@dotproto dotproto Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See this comment for details.

Suggested change
"id": "beastify@example.com",
"id": "beastify@mozilla.org",

@rebloor rebloor requested a review from dotproto November 17, 2025 22:45
@rebloor rebloor marked this pull request as ready for review November 19, 2025 22:31
Copy link
Contributor

@dotproto dotproto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the feedback in this review focuses on revising the Beastify extension to use async/await as that's the preferred pattern on MDN.

When both [async/await and Promises] are possible, we prefer using the simpler async/await syntax.

https://developer.mozilla.org/en-US/docs/MDN/Writing_guidelines/Code_style_guide/JavaScript#asynchronous_methods

Comment on lines 54 to 66
function reset(tabs) {
browser.tabs.removeCSS({code: hidePage}).then(() => {
browser.tabs.sendMessage(tabs[0].id, {
command: "reset",
const tabId = tabs[0].id;
browser.scripting
.removeCSS({
target: { tabId },
css: hidePage,
})
.then(() => {
browser.tabs.sendMessage(tabId, {
command: "reset",
});
});
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revise the function to use async/await.

Warning

This function has also been revised to expect a single tab rather than an array of tabs. As such, call sites will also need to be updated.

Suggested change
function reset(tabs) {
browser.tabs.removeCSS({code: hidePage}).then(() => {
browser.tabs.sendMessage(tabs[0].id, {
command: "reset",
const tabId = tabs[0].id;
browser.scripting
.removeCSS({
target: { tabId },
css: hidePage,
})
.then(() => {
browser.tabs.sendMessage(tabId, {
command: "reset",
});
});
});
}
async function reset(tab) {
await browser.scripting.removeCSS({
target: { tabId: tab.id },
css: hidePage,
});
browser.tabs.sendMessage(tabId, { command: "reset" });
}

Comment on lines 34 to 48
function beastify(tabs) {
browser.tabs.insertCSS({code: hidePage}).then(() => {
const url = beastNameToURL(e.target.textContent);
browser.tabs.sendMessage(tabs[0].id, {
command: "beastify",
beastURL: url
const tabId = tabs[0].id;
browser.scripting
.insertCSS({
target: { tabId },
css: hidePage,
})
.then(() => {
const url = beastNameToURL(e.target.textContent);
browser.tabs.sendMessage(tabId, {
command: "beastify",
beastURL: url,
});
});
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revise the function to use async/await.

Warning

This function now takes a single Tab rather than an array of Tabs. Call sites must be updated.

Suggested change
function beastify(tabs) {
browser.tabs.insertCSS({code: hidePage}).then(() => {
const url = beastNameToURL(e.target.textContent);
browser.tabs.sendMessage(tabs[0].id, {
command: "beastify",
beastURL: url
const tabId = tabs[0].id;
browser.scripting
.insertCSS({
target: { tabId },
css: hidePage,
})
.then(() => {
const url = beastNameToURL(e.target.textContent);
browser.tabs.sendMessage(tabId, {
command: "beastify",
beastURL: url,
});
});
});
}
async function beastify(tab) {
await browser.scripting.insertCSS({
target: { tabId: tab.id },
css: hidePage,
});
const url = beastNameToURL(e.target.textContent);
browser.tabs.sendMessage(tabId, {
command: "beastify",
beastURL: url,
});
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revise to use async/await instead of Promise chains.

* get the beast URL, and
* send a "beastify" message to the content script in the active tab.
*/
function beastify(tabs) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function beastify(tabs) {
async function beastify(tabs) {

Comment on lines 83 to 93
if (e.target.type === "reset") {
browser.tabs.query({active: true, currentWindow: true})
browser.tabs
.query({ active: true, currentWindow: true })
.then(reset)
.catch(reportError);
} else {
browser.tabs.query({active: true, currentWindow: true})
browser.tabs
.query({ active: true, currentWindow: true })
.then(beastify)
.catch(reportError);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revise the if block to use async/await conventions.

Since the query for the current tab is common to both blocks, we can move it above the if statements. Similarly, since the reportError error handler is common across both branches of the if statement, we can move it into a catch that wraps the entire if/else block.

Suggested change
if (e.target.type === "reset") {
browser.tabs.query({active: true, currentWindow: true})
browser.tabs
.query({ active: true, currentWindow: true })
.then(reset)
.catch(reportError);
} else {
browser.tabs.query({active: true, currentWindow: true})
browser.tabs
.query({ active: true, currentWindow: true })
.then(beastify)
.catch(reportError);
}
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
try {
if (e.target.type === "reset") {
await reset(tab);
} else {
await beastify(tab);
}
} catch (error) {
reportError(error);
}

Comment on lines 112 to 121
browser.tabs
.query({ active: true, currentWindow: true })
.then(([tab]) =>
browser.scripting.executeScript({
target: { tabId: tab.id },
files: ["/content_scripts/beastify.js"],
})
)
.then(listenForClicks)
.catch(reportExecuteScriptError);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
browser.tabs
.query({ active: true, currentWindow: true })
.then(([tab]) =>
browser.scripting.executeScript({
target: { tabId: tab.id },
files: ["/content_scripts/beastify.js"],
})
)
.then(listenForClicks)
.catch(reportExecuteScriptError);
(function runOnPopupOpened() {
try {
const [tab] = browser.tabs.query({ active: true, currentWindow: true });
await browser.scripting.executeScript({
target: { tabId: tab.id },
files: ["/content_scripts/beastify.js"],
});
listenForClicks();
} catch(e) {
reportExecuteScriptError(e);
}
})();

@rebloor rebloor requested a review from dotproto December 9, 2025 22:30
Copy link
Contributor

@dotproto dotproto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks solid. I also checked this branch branch out locally and did some additional testing; everything appeared to work as expected.

Co-authored-by: Simeon Vincent <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants