Skip to content

Commit

Permalink
Merge pull request #18681 from Rob--W/crx-mv3-migration
Browse files Browse the repository at this point in the history
[CRX] Migrate Chrome extension to Manifest Version 3
  • Loading branch information
timvandermeij authored Sep 8, 2024
2 parents 5785494 + 4bf7be6 commit a1b45d6
Show file tree
Hide file tree
Showing 14 changed files with 528 additions and 423 deletions.
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

0 comments on commit a1b45d6

Please sign in to comment.