Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CRX] Migrate Chrome extension to Manifest Version 3 #18681

Merged
merged 9 commits into from
Sep 8, 2024
Merged
19 changes: 19 additions & 0 deletions extensions/chromium/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,23 @@
"rules": {
"no-var": "off",
},

"overrides": [
{
// Include all files referenced in background.js
"files": [
"options/migration.js",
"preserve-referer.js",
"pdfHandler.js",
"extension-router.js",
"suppress-update.js",
"telemetry.js"
],
"env": {
// Background script is a service worker.
"browser": false,
"serviceworker": true
}
}
]
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<!doctype html>
<!--
Copyright 2015 Mozilla Foundation
/*
Copyright 2024 Mozilla Foundation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -13,5 +12,15 @@
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.
-->
<script src="restoretab.js"></script>
*/

"use strict";

importScripts(
"options/migration.js",
"preserve-referer.js",
"pdfHandler.js",
"extension-router.js",
"suppress-update.js",
"telemetry.js"
);
40 changes: 39 additions & 1 deletion extensions/chromium/contentscript.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ limitations under the License.

"use strict";

var VIEWER_URL = chrome.extension.getURL("content/web/viewer.html");
var VIEWER_URL = chrome.runtime.getURL("content/web/viewer.html");

function getViewerURL(pdf_url) {
return VIEWER_URL + "?file=" + encodeURIComponent(pdf_url);
}

document.addEventListener("animationstart", onAnimationStart, true);
if (document.contentType === "application/pdf") {
chrome.runtime.sendMessage({ action: "canRequestBody" }, maybeRenderPdfDoc);
}

function onAnimationStart(event) {
if (event.animationName === "pdfjs-detected-object-or-embed") {
Expand Down Expand Up @@ -221,3 +224,38 @@ function getEmbeddedViewerURL(path) {
path = a.href;
return getViewerURL(path) + fragment;
}

function maybeRenderPdfDoc(isNotPOST) {
if (!isNotPOST) {
// The document was loaded through a POST request, but we cannot access the
// original response body, nor safely send a new request to fetch the PDF.
// Until #4483 is fixed, POST requests should be ignored.
return;
}

// Detected PDF that was not redirected by the declarativeNetRequest rules.
// Maybe because this was served without Content-Type and sniffed as PDF.
// Or because this is Chrome 127-, which does not support responseHeaders
// condition in declarativeNetRequest (DNR), and PDF requests are therefore
// not redirected via DNR.

// In any case, load the viewer.
console.log(`Detected PDF via document, opening viewer for ${document.URL}`);

// Ideally we would use logic consistent with the DNR logic, like this:
// location.href = getEmbeddedViewerURL(document.URL);
// ... unfortunately, this causes Chrome to crash until version 129, fixed by
// https://chromium.googlesource.com/chromium/src/+/8c42358b2cc549553d939efe7d36515d80563da7%5E%21/
// Work around this by replacing the body with an iframe of the viewer.
// Interestingly, Chrome's built-in PDF viewer uses a similar technique.
const shadowRoot = document.body.attachShadow({ mode: "closed" });
const iframe = document.createElement("iframe");
iframe.style.position = "absolute";
iframe.style.top = "0";
iframe.style.left = "0";
iframe.style.width = "100%";
iframe.style.height = "100%";
iframe.style.border = "0 none";
iframe.src = getEmbeddedViewerURL(document.URL);
shadowRoot.append(iframe);
}
95 changes: 36 additions & 59 deletions extensions/chromium/extension-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ limitations under the License.
"use strict";

(function ExtensionRouterClosure() {
var VIEWER_URL = chrome.extension.getURL("content/web/viewer.html");
var CRX_BASE_URL = chrome.extension.getURL("/");
var VIEWER_URL = chrome.runtime.getURL("content/web/viewer.html");
var CRX_BASE_URL = chrome.runtime.getURL("/");

var schemes = [
"http",
Expand Down Expand Up @@ -55,73 +55,50 @@ limitations under the License.
return undefined;
}

// TODO(rob): Use declarativeWebRequest once declared URL-encoding is
// supported, see http://crbug.com/273589
// (or rewrite the query string parser in viewer.js to get it to
// recognize the non-URL-encoded PDF URL.)
chrome.webRequest.onBeforeRequest.addListener(
function (details) {
function resolveViewerURL(originalUrl) {
if (originalUrl.startsWith(CRX_BASE_URL)) {
// This listener converts chrome-extension://.../http://...pdf to
// chrome-extension://.../content/web/viewer.html?file=http%3A%2F%2F...pdf
var url = parseExtensionURL(details.url);
var url = parseExtensionURL(originalUrl);
if (url) {
url = VIEWER_URL + "?file=" + url;
var i = details.url.indexOf("#");
var i = originalUrl.indexOf("#");
if (i > 0) {
url += details.url.slice(i);
url += originalUrl.slice(i);
}
console.log("Redirecting " + details.url + " to " + url);
return { redirectUrl: url };
}
return undefined;
},
{
types: ["main_frame", "sub_frame"],
urls: schemes.map(function (scheme) {
// Format: "chrome-extension://[EXTENSIONID]/<scheme>*"
return CRX_BASE_URL + scheme + "*";
}),
},
["blocking"]
);

// When session restore is used, viewer pages may be loaded before the
// webRequest event listener is attached (= page not found).
// Or the extension could have been crashed (OOM), leaving a sad tab behind.
// Reload these tabs.
chrome.tabs.query(
{
url: CRX_BASE_URL + "*:*",
},
function (tabsFromLastSession) {
for (const { id } of tabsFromLastSession) {
chrome.tabs.reload(id);
return url;
}
}
);
console.log("Set up extension URL router.");
return undefined;
}

Object.keys(localStorage).forEach(function (key) {
// The localStorage item is set upon unload by chromecom.js.
var parsedKey = /^unload-(\d+)-(true|false)-(.+)/.exec(key);
if (parsedKey) {
var timeStart = parseInt(parsedKey[1], 10);
var isHidden = parsedKey[2] === "true";
var url = parsedKey[3];
if (Date.now() - timeStart < 3000) {
// Is it a new item (younger than 3 seconds)? Assume that the extension
// just reloaded, so restore the tab (work-around for crbug.com/511670).
chrome.tabs.create({
url:
chrome.runtime.getURL("restoretab.html") +
"?" +
encodeURIComponent(url) +
"#" +
encodeURIComponent(localStorage.getItem(key)),
active: !isHidden,
});
self.addEventListener("fetch", event => {
const req = event.request;
if (req.destination === "document") {
var url = resolveViewerURL(req.url);
if (url) {
console.log("Redirecting " + req.url + " to " + url);
event.respondWith(Response.redirect(url));
}
localStorage.removeItem(key);
}
});

// Ctrl + F5 bypasses service worker. the pretty extension URLs will fail to
// resolve in that case. Catch this and redirect to destination.
chrome.webNavigation.onErrorOccurred.addListener(
details => {
if (details.frameId !== 0) {
// Not a top-level frame. Cannot easily navigate a specific child frame.
return;
}
const url = resolveViewerURL(details.url);
if (url) {
console.log(`Redirecting ${details.url} to ${url} (fallback)`);
chrome.tabs.update(details.tabId, { url });
}
},
{ url: [{ urlPrefix: CRX_BASE_URL }] }
);

console.log("Set up extension URL router.");
})();
38 changes: 22 additions & 16 deletions extensions/chromium/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"minimum_chrome_version": "88",
"manifest_version": 2,
"minimum_chrome_version": "103",
"manifest_version": 3,
"name": "PDF Viewer",
"version": "PDFJSSCRIPT_VERSION",
"description": "Uses HTML5 to display PDF files directly in the browser.",
Expand All @@ -10,13 +10,14 @@
"16": "icon16.png"
},
"permissions": [
"alarms",
"declarativeNetRequestWithHostAccess",
"webRequest",
"webRequestBlocking",
"<all_urls>",
"tabs",
"webNavigation",
"storage"
],
"host_permissions": ["<all_urls>"],
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*", "file://*/*"],
Expand All @@ -30,23 +31,28 @@
"managed_schema": "preferences_schema.json"
},
"options_ui": {
"page": "options/options.html",
"chrome_style": true
"page": "options/options.html"
},
"options_page": "options/options.html",
"background": {
"page": "pdfHandler.html"
"service_worker": "background.js"
},
"incognito": "split",
"web_accessible_resources": [
"content/web/viewer.html",
"http:/*",
"https:/*",
"file:/*",
"chrome-extension:/*",
"blob:*",
"data:*",
"filesystem:/*",
"drive:*"
{
"resources": [
"content/web/viewer.html",
"http:/*",
"https:/*",
"file:/*",
"chrome-extension:/*",
"blob:*",
"data:*",
"filesystem:/*",
"drive:*"
],
"matches": ["<all_urls>"],
"extension_ids": ["*"]
}
]
}
26 changes: 13 additions & 13 deletions extensions/chromium/options/migration.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ 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.
*/
/* eslint strict: ["error", "function"] */
"use strict";

(function () {
"use strict";
chrome.runtime.onInstalled.addListener(({ reason }) => {
if (reason !== "update") {
// We only need to run migration logic for extension updates, not for new
// installs or browser updates.
return;
}
var storageLocal = chrome.storage.local;
var storageSync = chrome.storage.sync;

Expand All @@ -37,16 +41,12 @@ limitations under the License.
});
});

function getStorageNames(callback) {
var x = new XMLHttpRequest();
async function getStorageNames(callback) {
var schema_location = chrome.runtime.getManifest().storage.managed_schema;
x.open("get", chrome.runtime.getURL(schema_location));
x.onload = function () {
var storageKeys = Object.keys(x.response.properties);
callback(storageKeys);
};
x.responseType = "json";
x.send();
var res = await fetch(chrome.runtime.getURL(schema_location));
var storageManifest = await res.json();
var storageKeys = Object.keys(storageManifest.properties);
callback(storageKeys);
}

// Save |values| to storage.sync and delete the values with that key from
Expand Down Expand Up @@ -150,4 +150,4 @@ limitations under the License.
}
);
}
})();
});
13 changes: 9 additions & 4 deletions extensions/chromium/options/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,19 @@
<meta charset="utf-8">
<title>PDF.js viewer options</title>
<style>
/* TODO: Remove as much custom CSS as possible - crbug.com/446511 */
body {
min-width: 400px; /* a page at the settings page is at least 400px wide */
margin: 14px 17px; /* already added by default in Chrome 40.0.2212.0 */
}
.settings-row {
margin: 0.65em 0;
margin: 1em 0;
}
.checkbox label {
display: inline-flex;
align-items: center;
}
.checkbox label input {
flex-shrink: 0;
}
</style>
</head>
Expand All @@ -34,8 +40,7 @@
<button id="reset-button" type="button">Restore default settings</button>

<template id="checkbox-template">
<!-- Chromium's style: //src/extensions/renderer/resources/extension.css -->
<div class="checkbox">
<div class="settings-row checkbox">
<label>
<input type="checkbox">
<span></span>
Expand Down
22 changes: 0 additions & 22 deletions extensions/chromium/pdfHandler.html

This file was deleted.

Loading