diff --git a/gulpfile.js b/gulpfile.js index 8f4075e1..9aa19b2f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -26,6 +26,7 @@ var PATHS = { TARGET: { ROOT: "target/", CHROME: "target/chrome/", + FIREFOX: "target/firefox/", EDGE_ROOT: "target/edge/OneNoteWebClipper/edgeextension/", EDGE_EXTENSION: "target/edge/OneNoteWebClipper/edgeextension/manifest/extension/" }, @@ -207,6 +208,10 @@ gulp.task("bundleChrome", function () { return bundleEntry(PATHS.BUILDROOT + "scripts/extensions/chrome/", "chromeExtension.js"); }); +gulp.task("bundleFirefox", function () { + return bundleEntry(PATHS.BUILDROOT + "scripts/extensions/firefox/", "firefoxExtension.js"); +}); + gulp.task("bundleEdge", function () { return bundleEntry(PATHS.BUILDROOT + "scripts/extensions/edge/", "edgeExtension.js"); }); @@ -219,6 +224,7 @@ gulp.task("bundle", gulp.series( "bundleRenderer", "bundleLogManager", "bundleChrome", + "bundleFirefox", "bundleEdge" )); @@ -235,6 +241,7 @@ function lowerCasePathName() { var targetDirHasExportedCommonJs = {}; targetDirHasExportedCommonJs[PATHS.TARGET.CHROME] = false; +targetDirHasExportedCommonJs[PATHS.TARGET.FIREFOX] = false; targetDirHasExportedCommonJs[PATHS.TARGET.EDGE_EXTENSION] = false; function exportCommonJS(targetDir) { if (targetDirHasExportedCommonJs[targetDir]) { @@ -342,6 +349,31 @@ function exportChromeLibFiles() { return exportCommonLibFiles(PATHS.TARGET.CHROME); } +function exportFirefoxJS() { + var targetDir = PATHS.TARGET.FIREFOX; + var bundles = ["appendIsInstalledMarker.js", "regionOverlay.js", "contentCaptureInject.js", "renderer.js"]; + var bundleStreams = bundles.map(function(name) { + return gulp.src([PATHS.BUNDLEROOT + name]).pipe(concat(name)).pipe(gulp.dest(targetDir)); + }); + var backgroundTask = gulp.src([ + targetDir + "logManager.js", + PATHS.BUNDLEROOT + "firefoxExtension.js" + ]).pipe(concat("firefoxExtension.js")).pipe(gulp.dest(targetDir)); + + return exportCommonJS(targetDir).then(function() { + return streamsToPromise.apply(null, bundleStreams.concat([backgroundTask])); + }); +} + +function exportFirefoxSrcFiles() { + var targetDir = PATHS.TARGET.FIREFOX; + return Promise.all([ + exportCommonSrcFiles(targetDir), + exportCommonWebExtensionFiles(targetDir), + streamToPromise(gulp.src([PATHS.SRC.ROOT + "scripts/extensions/firefox/manifest.json"]).pipe(gulp.dest(targetDir))) + ]); +} + function exportEdgeJS() { var targetDir = PATHS.TARGET.EDGE_EXTENSION; @@ -443,6 +475,12 @@ gulp.task("exportChromeSrcFiles", function() { return exportChromeSrcFiles(); }) gulp.task("exportChromeLibFiles", function() { return exportChromeLibFiles(); }); gulp.task("exportChrome", gulp.series("exportChromeJS", "exportChromeCSS", "exportChromeSrcFiles", "exportChromeLibFiles")); +gulp.task("exportFirefoxJS", function() { return exportFirefoxJS(); }); +gulp.task("exportFirefoxCSS", function() { return exportCommonCSS(PATHS.TARGET.FIREFOX); }); +gulp.task("exportFirefoxSrcFiles", function() { return exportFirefoxSrcFiles(); }); +gulp.task("exportFirefoxLibFiles", function() { return exportCommonLibFiles(PATHS.TARGET.FIREFOX); }); +gulp.task("exportFirefox", gulp.series("exportFirefoxJS", "exportFirefoxCSS", "exportFirefoxSrcFiles", "exportFirefoxLibFiles")); + gulp.task("exportEdgeJS", function() { return exportEdgeJS(); }); gulp.task("exportEdgeCSS", function() { return exportEdgeCSS(); }); gulp.task("exportEdgeSrcFiles", function() { return exportEdgeSrcFiles(); }); @@ -450,11 +488,11 @@ gulp.task("exportEdgePackageFiles", function() { return exportEdgePackageFiles() gulp.task("exportEdgeLibFiles", function() { return exportEdgeLibFiles(); }); gulp.task("exportEdge", gulp.series("exportEdgeJS", "exportEdgeCSS", "exportEdgeSrcFiles", "exportEdgePackageFiles", "exportEdgeLibFiles")); -gulp.task("exportJS", gulp.series("exportChromeJS", "exportEdgeJS")); -gulp.task("exportCSS", gulp.series("exportChromeCSS", "exportEdgeCSS")); -gulp.task("exportSrcFiles", gulp.series("exportChromeSrcFiles", "exportEdgeSrcFiles")); +gulp.task("exportJS", gulp.series("exportChromeJS", "exportFirefoxJS", "exportEdgeJS")); +gulp.task("exportCSS", gulp.series("exportChromeCSS", "exportFirefoxCSS", "exportEdgeCSS")); +gulp.task("exportSrcFiles", gulp.series("exportChromeSrcFiles", "exportFirefoxSrcFiles", "exportEdgeSrcFiles")); -gulp.task("export", gulp.series("exportAllCommonJS", "exportChrome", "exportEdge")); +gulp.task("export", gulp.series("exportAllCommonJS", "exportChrome", "exportFirefox", "exportEdge")); //////////////////////////////////////// // PACKAGING TASKS @@ -465,7 +503,13 @@ gulp.task("packageChrome", function() { .pipe(gulp.dest(PATHS.TARGET.CHROME)); }); -gulp.task("package", gulp.series("packageChrome")); +gulp.task("packageFirefox", function() { + return gulp.src([PATHS.TARGET.FIREFOX + "/**/*", "!" + PATHS.TARGET.FIREFOX + "/OneNoteWebClipper.xpi"], { encoding: false }) + .pipe(zip("OneNoteWebClipper.xpi")) + .pipe(gulp.dest(PATHS.TARGET.FIREFOX)); +}); + +gulp.task("package", gulp.series("packageChrome", "packageFirefox")); //////////////////////////////////////// // PRODUCTION-ONLY TASKS @@ -513,6 +557,7 @@ gulp.task("watchSrcFiles", function() { PATHS.SRC.ROOT + "images/*", PATHS.SRC.ROOT + "renderer.html", PATHS.SRC.ROOT + "scripts/extensions/chrome/manifest.json", + PATHS.SRC.ROOT + "scripts/extensions/firefox/manifest.json", PATHS.SRC.ROOT + "scripts/extensions/offscreen.html", PATHS.SRC.ROOT + "scripts/extensions/edge/edgeExtension.html", PATHS.SRC.ROOT + "scripts/extensions/edge/manifest.json" diff --git a/src/scripts/communicator/offscreenCommunicator.ts b/src/scripts/communicator/offscreenCommunicator.ts index 6f92863f..55b10c91 100644 --- a/src/scripts/communicator/offscreenCommunicator.ts +++ b/src/scripts/communicator/offscreenCommunicator.ts @@ -35,6 +35,10 @@ export async function sendToOffscreenDocument(type: string, data: any): Promise< let chromeRuntime = chrome.runtime as any; let chromeOffscreen = (chrome as any).offscreen; + if (typeof chromeRuntime.getContexts !== "function" || !chromeOffscreen) { + return handleWithoutOffscreenDocument(type, data); + } + const existingContexts = await chromeRuntime.getContexts({ contextTypes: [chromeRuntime.ContextType.OFFSCREEN_DOCUMENT], documentUrls: [offscreenUrl] @@ -64,3 +68,26 @@ export async function sendToOffscreenDocument(type: string, data: any): Promise< }); }); } + +function handleWithoutOffscreenDocument(type: string, data: any): string { + switch (type) { + case OffscreenMessageTypes.getFromLocalStorage: + return localStorage.getItem(data.key); + case OffscreenMessageTypes.setToLocalStorage: + localStorage.setItem(data.key, data.value); + return "SUCCESS"; + case OffscreenMessageTypes.removeFromLocalStorage: + localStorage.removeItem(data.key); + return "SUCCESS"; + case OffscreenMessageTypes.getHostname: { + let url = new URL(data.url, offscreenUrl); + return url.protocol + "//" + url.host + "/"; + } + case OffscreenMessageTypes.getPathname: + return new URL(data.url, offscreenUrl).pathname; + default: + // eslint-disable-next-line no-console -- match the offscreen handler's failure mode + console.warn(`Unexpected message type received: '${type}'.`); + return; + } +} diff --git a/src/scripts/extensions/firefox/firefoxExtension.ts b/src/scripts/extensions/firefox/firefoxExtension.ts new file mode 100644 index 00000000..a95a9a55 --- /dev/null +++ b/src/scripts/extensions/firefox/firefoxExtension.ts @@ -0,0 +1,6 @@ +import {ClientType} from "../../clientType"; +import {WebExtension} from "../webExtensionBase/webExtension"; + +// Firefox exposes the callback-based chrome namespace used by the shared implementation. +WebExtension.browser = chrome; +let clipperBackground = new WebExtension(ClientType.FirefoxExtension); diff --git a/src/scripts/extensions/firefox/manifest.json b/src/scripts/extensions/firefox/manifest.json new file mode 100644 index 00000000..2cbf1e27 --- /dev/null +++ b/src/scripts/extensions/firefox/manifest.json @@ -0,0 +1,64 @@ +{ + "manifest_version": 3, + "name": "OneNote Web Clipper", + "description": "__MSG_appDesc__", + "default_locale": "en", + "version": "3.11.2", + "browser_specific_settings": { + "gecko": { + "id": "Clipper@OneNote.com" + } + }, + "background": { + "scripts": [ + "firefoxExtension.js" + ] + }, + "content_scripts": [ + { + "matches": [ + "https://onenote.officeapps.live.com/*", + "https://ppc-onenote.officeapps.live.com/*", + "https://onenote.officeapps-df.live.com/*", + "https://onenote.officeapps.live-int.com/*" + ], + "js": [ + "appendIsInstalledMarker.js" + ], + "run_at": "document_start", + "all_frames": true + } + ], + "permissions": [ + "activeTab", + "scripting", + "contextMenus", + "tabs", + "webRequest", + "webNavigation" + ], + "host_permissions": [ + "" + ], + "content_security_policy": { + "extension_pages": "script-src 'self'; object-src 'self'" + }, + "icons": { + "16": "icons/icon-16.png", + "19": "icons/icon-19.png", + "32": "icons/icon-32.png", + "38": "icons/icon-38.png", + "48": "icons/icon-48.png", + "64": "icons/icon-64.png", + "96": "icons/icon-96.png", + "128": "icons/icon-128.png", + "256": "icons/icon-256.png" + }, + "action": { + "default_title": "OneNote Web Clipper", + "default_icon": { + "19": "icons/icon-19.png", + "38": "icons/icon-38.png" + } + } +}