Skip to content
This repository has been archived by the owner on Jan 17, 2023. It is now read-only.

Commit

Permalink
Fix #2843, lazily load code into the background page
Browse files Browse the repository at this point in the history
This takes a minimal approach, loading scripts using the script tag on the first button click.
Also creates and intercepts the contextMenu action, and any incoming communication
Reads onboarding flag and changes icon as necessary
  • Loading branch information
ianb committed Jun 7, 2017
1 parent a6b24a0 commit 8f98579
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 34 deletions.
5 changes: 3 additions & 2 deletions addon/webextension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This contains all the files for the WebExtension. Most files are not "built", b
1. The background page, in `background/`
2. The selector content worker, in `selector/`
3. The site helper that communicates with the Firefox Screenshots website, in `site-helper.js`
- The background process is loaded according to a list in `manifest.json.template` – if there are load order dependencies, they must be manually managed with that list
- The background process is loaded according to a list in `background/startBackground.js` – if there are load order dependencies, they must be manually managed with that list
- The selector content worker is loaded with a list in `background/selectorLoader.js`. Again this must be manually maintained.
- The site helper worker is loaded via a separate list in `manifest.json.template`

Expand All @@ -24,7 +24,8 @@ To support communication, `background/communication.js` handles incoming message

The basic flow:

1. Everything starts when the button is clicked. This fires an event in `background/main.js`
1. A minimal file is loaded initially, `background/startBackground.js` which listens for clicks.
1. In response to a click, other files are loaded (these are listed in `startBackground.js`) and `main.onClicked()` called.
2. The background page loads the content worker with `background/selectorLoader.js`
3. `selector/shooter.js` handles most communication logic from the selector side
4. `shooter.js` collects the information and creates a Shot object.
Expand Down
3 changes: 2 additions & 1 deletion addon/webextension/background/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
These are all the files/modules for the background worker. This is the higher-privileged WebExtension worker that has no interface.

- `startBackground.js` handles the browserAction click event and context menu, and loads everything else on the first button of context menu click.
- `analytics.js` handles sending events to the server for use with GA.
- `auth.js` handles the initial authentication call, and handles subsequent requests using that authentication. It also informs `analytics.js` of any A/B tests so those can be submitted.
- `clipboard.js` handles copying to the clipboard.
- `communication.js` handles communication with the content workers.
- `deviceinfo.js` gets information about this device that is used in some logging and analytics.
- `selectorLoader.js` handles loading the selector worker, and contains the list of files that the selector loads.
- `main.js` loads and launches everything.
- `main.js` launches everything
- `takeshot.js` takes the shot the content worker has constructed, sends it to the server, and handles copying and opening the tab.
4 changes: 2 additions & 2 deletions addon/webextension/background/communication.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ this.communication = (function() {

let registeredFunctions = {};

browser.runtime.onMessage.addListener(catcher.watchFunction((req, sender, sendResponse) => {
exports.onMessage = catcher.watchFunction((req, sender, sendResponse) => {
if (!(req.funcName in registeredFunctions)) {
log.error(`Received unknown internal message type ${req.funcName}`);
sendResponse({type: "error", name: "Unknown message type"});
Expand Down Expand Up @@ -39,7 +39,7 @@ this.communication = (function() {
return true;
}
sendResponse({type: "success", value: result});
}));
});

exports.register = function(name, func) {
registeredFunctions[name] = func;
Expand Down
21 changes: 5 additions & 16 deletions addon/webextension/background/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ this.main = (function() {
return /^about:(?:newtab|blank)/i.test(url) || /^resource:\/\/activity-streams\//i.test(url);
}

browser.browserAction.onClicked.addListener(catcher.watchFunction((tab) => {
// This is called by startBackground.js, directly in response to browser.browserAction.onClicked
exports.onClicked = catcher.watchFunction((tab) => {
if (shouldOpenMyShots(tab.url)) {
if (!hasSeenOnboarding) {
catcher.watchPromise(analytics.refreshTelemetryPref().then(() => {
Expand Down Expand Up @@ -116,27 +117,15 @@ this.main = (function() {
throw error;
}));
}
}));
});

function forceOnboarding() {
return browser.tabs.create({url: getOnboardingUrl()}).then((tab) => {
return toggleSelector(tab);
});
}

browser.contextMenus.create({
id: "create-screenshot",
title: browser.i18n.getMessage("contextMenuLabel"),
contexts: ["page"],
documentUrlPatterns: ["<all_urls>"]
}, () => {
// Note: unlike most browser.* functions this one does not return a promise
if (browser.runtime.lastError) {
catcher.unhandled(new Error(browser.runtime.lastError.message));
}
});

browser.contextMenus.onClicked.addListener(catcher.watchFunction((info, tab) => {
exports.onClickedContextMenu = catcher.watchFunction((info, tab) => {
if (!tab) {
// Not in a page/tab context, ignore
return;
Expand All @@ -150,7 +139,7 @@ this.main = (function() {
catcher.watchPromise(
toggleSelector(tab)
.then(() => sendEvent("start-shot", "context-menu")));
}));
});

function urlEnabled(url) {
if (shouldOpenMyShots(url)) {
Expand Down
99 changes: 99 additions & 0 deletions addon/webextension/background/startBackground.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* globals browser, main, communication */
/* This file handles:
browser.browserAction.onClicked
browser.contextMenus.onClicked
browser.runtime.onMessage
and loads the rest of the background page in response to those events, forwarding
the events to main.onClicked, main.onClickedContextMenu, or communication.onMessage
*/

this.startBackground = (function() {
const backgroundScripts = [
"log.js",
"makeUuid.js",
"catcher.js",
"background/selectorLoader.js",
"background/communication.js",
"background/auth.js",
"background/senderror.js",
"build/raven.js",
"build/shot.js",
"background/analytics.js",
"background/deviceInfo.js",
"background/takeshot.js",
"background/main.js"
];

browser.browserAction.onClicked.addListener((tab) => {
loadIfNecessary().then(() => {
main.onClicked(tab);
}).catch((error) => {
console.error("Error loading Screenshots:", error);
});
});


browser.contextMenus.create({
id: "create-screenshot",
title: browser.i18n.getMessage("contextMenuLabel"),
contexts: ["page"],
documentUrlPatterns: ["<all_urls>"]
});

browser.contextMenus.onClicked.addListener((info, tab) => {
loadIfNecessary().then(() => {
main.onClickedContextMenu(info, tab);
}).catch((error) => {
console.error("Error loading Screenshots:", error);
});
});

// Note this duplicates functionality in main.js, but we need to change
// the onboarding icon before main.js loads up
browser.storage.local.get(["hasSeenOnboarding"]).then((result) => {
let hasSeenOnboarding = !!result.hasSeenOnboarding;
if (!hasSeenOnboarding) {
let path = "icons/icon-starred-32.svg";
browser.browserAction.setIcon({path});
}
}).catch((error) => {
console.error("Error loading Screenshots onboarding flag:", error);
});

browser.runtime.onMessage.addListener((req, sender, sendResponse) => {
loadIfNecessary().then(() => {
return communication.onMessage(req, sender, sendResponse);
}).catch((error) => {
console.error("Error loading Screenshots:", error);
});
return true;
});

let loadedPromise;

function loadIfNecessary() {
if (loadedPromise) {
return loadedPromise;
}
loadedPromise = Promise.resolve();
backgroundScripts.forEach((script) => {
loadedPromise = loadedPromise.then(() => {
return new Promise((resolve, reject) => {
let tag = document.createElement("script");
tag.src = browser.extension.getURL(script);
tag.onload = () => {
resolve();
};
tag.onerror = (error) => {
let exc = new Error(`Error loading script: ${error.message}`);
exc.scriptName = script;
reject(exc);
};
document.head.appendChild(tag);
});
});
});
return loadedPromise;
}

})();
14 changes: 1 addition & 13 deletions addon/webextension/manifest.json.template
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,7 @@
"background": {
"scripts": [
"build/buildSettings.js",
"log.js",
"makeUuid.js",
"catcher.js",
"background/selectorLoader.js",
"background/communication.js",
"background/auth.js",
"background/senderror.js",
"build/raven.js",
"build/shot.js",
"background/analytics.js",
"background/deviceInfo.js",
"background/takeshot.js",
"background/main.js"
"background/startBackground.js"
]
},
"content_scripts": [
Expand Down

0 comments on commit 8f98579

Please # to comment.