mirror of
https://github.com/GuySandler/CanvasRefined.git
synced 2026-09-22 04:54:53 +02:00
caching todo list in hopes of better performance
This commit is contained in:
parent
16ed09ff43
commit
b7a819b9fc
@ -1183,6 +1183,22 @@
|
|||||||
<span>This will reset all settings to the default. You will need to refresh the page after.</span>
|
<span>This will reset all settings to the default. You will need to refresh the page after.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="option-container">
|
||||||
|
<h3 class="header-small">Clear planner cache</h3>
|
||||||
|
<div style="display: flex; gap: 10px; align-items: center;">
|
||||||
|
<button class="big-button" id="planner-cache-clear-btn">Go</button>
|
||||||
|
<span>This deletes the cached to-do list data (assignments, announcements, reminders) so it is re-fetched fresh from Canvas. Open Canvas tabs update automatically.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="option-container">
|
||||||
|
<h3 class="header-small">Storage usage</h3>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 3px;">
|
||||||
|
<span class="sub-text">Settings (sync): <span id="storage-usage-sync">…</span></span>
|
||||||
|
<span class="sub-text">Planner cache: <span id="storage-usage-cache">…</span></span>
|
||||||
|
<span class="sub-text">Other local data: <span id="storage-usage-local">…</span></span>
|
||||||
|
<span class="sub-text">Local quota: <span id="storage-usage-quota">…</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="option-container">
|
<div class="option-container">
|
||||||
<h3 class="header-small">Storage Dump</h3>
|
<h3 class="header-small">Storage Dump</h3>
|
||||||
<div style="margin-bottom: 10px">
|
<div style="margin-bottom: 10px">
|
||||||
|
|||||||
265
js/content.js
265
js/content.js
@ -1623,6 +1623,19 @@ function recieveMessage(request, sender, sendResponse) {
|
|||||||
case ("inspect"): sendResponse(inspectDarkMode(true)); break;
|
case ("inspect"): sendResponse(inspectDarkMode(true)); break;
|
||||||
case ("fixdm"): sendResponse(runDarkModeFixer(true)); break;
|
case ("fixdm"): sendResponse(runDarkModeFixer(true)); break;
|
||||||
case ("updateBackground"): applyCustomBackground(); sendResponse(true); break;
|
case ("updateBackground"): applyCustomBackground(); sendResponse(true); break;
|
||||||
|
case ("clearPlannerCache"):
|
||||||
|
// "Clear planner cache" (Report issue tab in the popup): drop
|
||||||
|
// the cache, re-fetch fresh planner data, and re-render every
|
||||||
|
// consumer in this tab.
|
||||||
|
(async () => {
|
||||||
|
try { await chrome.storage.local.remove(PLANNER_CACHE_KEY); } catch (e) { /* nothing stored */ }
|
||||||
|
if (options.assignments_due === true || options.better_todo === true) {
|
||||||
|
const items = await loadPlannerItems();
|
||||||
|
refreshPlannerConsumers(items);
|
||||||
|
}
|
||||||
|
sendResponse(true);
|
||||||
|
})();
|
||||||
|
return true; // keep the message channel open for async sendResponse
|
||||||
default: sendResponse(true);
|
default: sendResponse(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6991,32 +7004,118 @@ function changeFavicon() {
|
|||||||
|
|
||||||
function getAssignments() {
|
function getAssignments() {
|
||||||
if (options.assignments_due === true || options.better_todo === true) {
|
if (options.assignments_due === true || options.better_todo === true) {
|
||||||
// Fetch planner items from as far back as possible so overdue tasks
|
assignments = loadPlannerItems();
|
||||||
// always appear, no matter how long ago they were due. The planner
|
|
||||||
// API defaults start_date to "now" (which would hide every overdue
|
|
||||||
// item), so a far-past start date is required. Canvas returns planner
|
|
||||||
// items oldest-first in pages, so every page must be followed — a
|
|
||||||
// single request would only return the oldest page and silently drop
|
|
||||||
// all recent items.
|
|
||||||
assignments = getAllPlannerItems();
|
|
||||||
cardAssignments = preloadAssignmentEls();
|
cardAssignments = preloadAssignmentEls();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Far-past start date for the planner items fetch. Concluded courses are
|
// ===================== Planner items (cached) =====================
|
||||||
// excluded by the API by default, so this only pulls history from the user's
|
// The planner list powers the to-do list, dashboard cards, reminders, and
|
||||||
// currently active courses, which keeps the payload bounded.
|
// progress rings. It used to be re-fetched from 2000-01-01 on every page
|
||||||
const PLANNER_START_DATE = "2000-01-01";
|
// load — dozens of sequential requests for students with long histories,
|
||||||
|
// which was the main cause of the 1.0.0 slow-load reports. Now:
|
||||||
|
// - items are cached in chrome.storage.local and served instantly,
|
||||||
|
// - each page load only fetches a small recent window (14 days),
|
||||||
|
// - a full lookback walk (max 1 year) runs at most once a week, and only
|
||||||
|
// while the tab is idle, and
|
||||||
|
// - items are limited to courses the student is currently enrolled in.
|
||||||
|
// Canvas returns planner items oldest-first, so any fetch that starts far
|
||||||
|
// in the past must page through everything newer; capping the lookback at
|
||||||
|
// one year bounds both the request count and the cache size. Overdue items
|
||||||
|
// older than a year are intentionally dropped. (Concluded courses are
|
||||||
|
// excluded by the planner API by default, so this is history from the
|
||||||
|
// user's active courses only.)
|
||||||
|
const PLANNER_CACHE_KEY = "planner_cache_v1";
|
||||||
|
const PLANNER_LOOKBACK_DAYS = 365; // max history fetched from Canvas
|
||||||
|
const PLANNER_WINDOW_DAYS = 14; // per-load "what changed" refresh window
|
||||||
|
const PLANNER_FULL_REFRESH_DAYS = 7; // min time between full lookback walks
|
||||||
// Hard cap on pages fetched (50 pages * 100 items = 5000 items) as a safety
|
// Hard cap on pages fetched (50 pages * 100 items = 5000 items) as a safety
|
||||||
// net against a malformed/misbehaving next link.
|
// net against a malformed/misbehaving next link.
|
||||||
const PLANNER_MAX_PAGES = 50;
|
const PLANNER_MAX_PAGES = 50;
|
||||||
|
|
||||||
// Fetches every page of /api/v1/planner/items since PLANNER_START_DATE.
|
function plannerDateDaysAgo(days) {
|
||||||
// Uses the same session/headers as getData but follows the Link "next"
|
return new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);
|
||||||
// headers until exhausted.
|
}
|
||||||
async function getAllPlannerItems() {
|
|
||||||
|
// Stable identity for a planner item (same pair the complete-toggle uses).
|
||||||
|
function plannerItemKey(item) {
|
||||||
|
return `${item.plannable_type}|${item.plannable_id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== Active-enrollment filter =====================
|
||||||
|
// Reported bug: the to-do list showed assignments/announcements from old,
|
||||||
|
// concluded classes. The planner API excludes concluded *courses*, but not
|
||||||
|
// courses where only the student's *enrollment* has concluded (common at
|
||||||
|
// term boundaries), so those items still leak through. This fetches the ids
|
||||||
|
// of the user's currently-active course enrollments so planner items can be
|
||||||
|
// filtered to real, current classes. Returns null on failure so callers
|
||||||
|
// skip filtering instead of hiding everything.
|
||||||
|
async function fetchActiveCourseIds() {
|
||||||
|
const ids = new Set();
|
||||||
|
let url = `${domain}/api/v1/courses?enrollment_state=active&per_page=100`;
|
||||||
|
// 10 pages * 100 courses is far beyond any real enrollment count; just a
|
||||||
|
// safety net against a malformed next link.
|
||||||
|
for (let page = 0; page < 10 && url; page++) {
|
||||||
|
let response;
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
response = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
data = await response.json();
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!response.ok || !Array.isArray(data)) return null;
|
||||||
|
for (const course of data) {
|
||||||
|
if (course && course.id != null) ids.add(String(course.id));
|
||||||
|
}
|
||||||
|
url = getNextPageUrl(response.headers.get("Link"));
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keeps only items belonging to courses in `courseIds` (Set or array).
|
||||||
|
// null/undefined means "unknown" (the enrollment fetch failed), so no
|
||||||
|
// filtering is applied. Personal items with no course context (planner
|
||||||
|
// notes, non-course contexts like groups) are always kept.
|
||||||
|
function filterPlannerItemsByActiveCourses(items, courseIds) {
|
||||||
|
if (!courseIds) return items;
|
||||||
|
const active = (courseIds instanceof Set) ? courseIds : new Set(courseIds.map(String));
|
||||||
|
return items.filter(item => {
|
||||||
|
const cid = item.course_id ?? (item.context_type === "course" ? item.context_id : null);
|
||||||
|
return cid == null || active.has(String(cid));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readPlannerCache() {
|
||||||
|
try {
|
||||||
|
const result = await chrome.storage.local.get(PLANNER_CACHE_KEY);
|
||||||
|
const cache = result && result[PLANNER_CACHE_KEY];
|
||||||
|
if (cache && Array.isArray(cache.items)) return cache;
|
||||||
|
} catch (e) { /* storage unavailable; fall back to live fetch */ }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writePlannerCache(items, lastFullRefresh, activeCourseIds) {
|
||||||
|
try {
|
||||||
|
// Fire-and-forget; guard both sync throws and (in MV3) promise
|
||||||
|
// rejection, e.g. a quota error — losing the cache is non-fatal.
|
||||||
|
const p = chrome.storage.local.set({ [PLANNER_CACHE_KEY]: { items, lastFullRefresh, activeCourseIds } });
|
||||||
|
if (p && typeof p.catch === "function") p.catch(() => {});
|
||||||
|
} catch (e) { /* cache write failure is non-fatal */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetches every page of /api/v1/planner/items with a due date on/after
|
||||||
|
// `startDate`, following the Link "next" headers until exhausted. Uses the
|
||||||
|
// same session/headers as getData.
|
||||||
|
async function fetchPlannerItemsSince(startDate) {
|
||||||
const allItems = [];
|
const allItems = [];
|
||||||
let url = `${domain}/api/v1/planner/items?start_date=${PLANNER_START_DATE}&per_page=100`;
|
let url = `${domain}/api/v1/planner/items?start_date=${startDate}&per_page=100`;
|
||||||
for (let page = 0; page < PLANNER_MAX_PAGES && url; page++) {
|
for (let page = 0; page < PLANNER_MAX_PAGES && url; page++) {
|
||||||
let response;
|
let response;
|
||||||
let data;
|
let data;
|
||||||
@ -7036,14 +7135,140 @@ async function getAllPlannerItems() {
|
|||||||
// Deep-clone via JSON to unwrap Firefox Xray objects so nested props
|
// Deep-clone via JSON to unwrap Firefox Xray objects so nested props
|
||||||
// are mutable (same as getData).
|
// are mutable (same as getData).
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(JSON.stringify(data));
|
allItems.push(...JSON.parse(JSON.stringify(data)));
|
||||||
} catch (_) { /* keep original */ }
|
} catch (_) {
|
||||||
allItems.push(...data);
|
allItems.push(...data);
|
||||||
|
}
|
||||||
url = getNextPageUrl(response.headers.get("Link"));
|
url = getNextPageUrl(response.headers.get("Link"));
|
||||||
}
|
}
|
||||||
return allItems;
|
return allItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merges a fresh window fetch into the cached list. Cached items whose due
|
||||||
|
// date falls inside the fetched window are replaced wholesale (catches new,
|
||||||
|
// changed, and removed items); older cached items are kept as-is.
|
||||||
|
function mergePlannerItems(cached, fetched, windowStartMs) {
|
||||||
|
const byKey = new Map();
|
||||||
|
for (const item of cached) {
|
||||||
|
const due = new Date(item.plannable_date).getTime();
|
||||||
|
if (due >= windowStartMs) continue; // superseded by the fresh fetch
|
||||||
|
byKey.set(plannerItemKey(item), item);
|
||||||
|
}
|
||||||
|
for (const item of fetched) byKey.set(plannerItemKey(item), item);
|
||||||
|
return sortAndTrimPlannerItems([...byKey.values()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drops items older than the lookback window and returns the list sorted by
|
||||||
|
// due date ascending (the order the rest of the extension expects).
|
||||||
|
function sortAndTrimPlannerItems(items) {
|
||||||
|
const cutoff = Date.now() - PLANNER_LOOKBACK_DAYS * 86400000;
|
||||||
|
const trimmed = items.filter(item => new Date(item.plannable_date).getTime() >= cutoff);
|
||||||
|
trimmed.sort((a, b) => new Date(a.plannable_date) - new Date(b.plannable_date));
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cheap change signature so consumers only re-render when something they
|
||||||
|
// display actually moved: due date, submitted/graded/complete state, or an
|
||||||
|
// announcement's read state.
|
||||||
|
function plannerFingerprint(items) {
|
||||||
|
return items.map(item =>
|
||||||
|
`${plannerItemKey(item)}:${item.plannable_date}:${item.submissions?.submitted ? 1 : 0}:${item.submissions?.graded ? 1 : 0}:${item.planner_override?.marked_complete ? 1 : 0}:${item.plannable?.read_state ?? ""}`
|
||||||
|
).join("|");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry point for the planner data. Resolves instantly from cache when
|
||||||
|
// present; otherwise does one bounded (1-year) fetch on the critical path so
|
||||||
|
// the to-do list isn't empty on first run, then caches the result. In both
|
||||||
|
// cases items are limited to the student's currently-active courses.
|
||||||
|
async function loadPlannerItems() {
|
||||||
|
const cache = await readPlannerCache();
|
||||||
|
if (cache) {
|
||||||
|
schedulePlannerRefresh(cache);
|
||||||
|
// Serve instantly, filtered with the last known enrollment list;
|
||||||
|
// the background refresh below brings that list up to date.
|
||||||
|
return filterPlannerItemsByActiveCourses(cache.items, cache.activeCourseIds);
|
||||||
|
}
|
||||||
|
// First run (no cache yet): fetch the bounded lookback and the active
|
||||||
|
// enrollment list in parallel, then cache the filtered result.
|
||||||
|
const [items, courseIds] = await Promise.all([
|
||||||
|
fetchPlannerItemsSince(plannerDateDaysAgo(PLANNER_LOOKBACK_DAYS)),
|
||||||
|
fetchActiveCourseIds(),
|
||||||
|
]);
|
||||||
|
const filtered = filterPlannerItemsByActiveCourses(
|
||||||
|
sortAndTrimPlannerItems(items), courseIds
|
||||||
|
);
|
||||||
|
writePlannerCache(filtered, Date.now(), courseIds ? [...courseIds] : null);
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background cache refresh: a small recent window on every load (catches new
|
||||||
|
// items and recent changes), plus a full lookback walk at most once every
|
||||||
|
// PLANNER_FULL_REFRESH_DAYS (catches state changes on older items, e.g. a
|
||||||
|
// months-old assignment finally being submitted). The active-enrollment list
|
||||||
|
// is refreshed on every pass so concluded classes drop out promptly rather
|
||||||
|
// than waiting for the weekly walk. Runs while the tab is idle so it never
|
||||||
|
// competes with page load.
|
||||||
|
function schedulePlannerRefresh(cache) {
|
||||||
|
const fullRefreshDue = !cache.lastFullRefresh ||
|
||||||
|
(Date.now() - cache.lastFullRefresh > PLANNER_FULL_REFRESH_DAYS * 86400000);
|
||||||
|
const run = async () => {
|
||||||
|
try {
|
||||||
|
const before = plannerFingerprint(
|
||||||
|
filterPlannerItemsByActiveCourses(cache.items, cache.activeCourseIds)
|
||||||
|
);
|
||||||
|
const courseIds = await fetchActiveCourseIds();
|
||||||
|
// On enrollment-fetch failure, fall back to the cached list.
|
||||||
|
const active = courseIds ?? (cache.activeCourseIds ?? null);
|
||||||
|
const idsToStore = courseIds ? [...courseIds] : (cache.activeCourseIds ?? null);
|
||||||
|
let merged;
|
||||||
|
if (fullRefreshDue) {
|
||||||
|
merged = filterPlannerItemsByActiveCourses(
|
||||||
|
sortAndTrimPlannerItems(
|
||||||
|
await fetchPlannerItemsSince(plannerDateDaysAgo(PLANNER_LOOKBACK_DAYS))
|
||||||
|
),
|
||||||
|
active
|
||||||
|
);
|
||||||
|
writePlannerCache(merged, Date.now(), idsToStore);
|
||||||
|
} else {
|
||||||
|
const windowStart = plannerDateDaysAgo(PLANNER_WINDOW_DAYS);
|
||||||
|
const fetched = await fetchPlannerItemsSince(windowStart);
|
||||||
|
merged = filterPlannerItemsByActiveCourses(
|
||||||
|
mergePlannerItems(cache.items, fetched, Date.parse(windowStart)),
|
||||||
|
active
|
||||||
|
);
|
||||||
|
writePlannerCache(merged, cache.lastFullRefresh, idsToStore);
|
||||||
|
}
|
||||||
|
if (plannerFingerprint(merged) !== before) refreshPlannerConsumers(merged);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("planner refresh failed", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (typeof requestIdleCallback === "function") {
|
||||||
|
requestIdleCallback(() => run(), { timeout: 20000 });
|
||||||
|
} else {
|
||||||
|
setTimeout(run, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-renders everything that displays planner items after the background
|
||||||
|
// refresh produced new data. Mirrors the option-change handlers so all the
|
||||||
|
// existing consumers re-attach to the updated promise.
|
||||||
|
function refreshPlannerConsumers(items) {
|
||||||
|
assignments = Promise.resolve(items);
|
||||||
|
updateReminders();
|
||||||
|
if (options.assignments_due === true || options.better_todo === true) {
|
||||||
|
cardAssignments = preloadAssignmentEls();
|
||||||
|
loadCardAssignments();
|
||||||
|
}
|
||||||
|
if (options.better_todo && document.getElementById("better-todo-main")) {
|
||||||
|
moreAnnouncementCount = 0;
|
||||||
|
moreAssignmentCount = 0;
|
||||||
|
moreCompletedCount = 0;
|
||||||
|
clearTodoList();
|
||||||
|
createTodoSections(document.querySelector("#canvasrefined-todo-list"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Extracts the rel="next" URL from a Canvas pagination Link header, or
|
// Extracts the rel="next" URL from a Canvas pagination Link header, or
|
||||||
// returns null when on the last page.
|
// returns null when on the last page.
|
||||||
function getNextPageUrl(linkHeader) {
|
function getNextPageUrl(linkHeader) {
|
||||||
|
|||||||
86
js/popup.js
86
js/popup.js
@ -1249,8 +1249,94 @@ function setup() {
|
|||||||
// activate storage reset button
|
// activate storage reset button
|
||||||
document.querySelector("#storage-reset-btn").addEventListener("click", () => {
|
document.querySelector("#storage-reset-btn").addEventListener("click", () => {
|
||||||
chrome.storage.sync.set(defaultOptions["sync"]);
|
chrome.storage.sync.set(defaultOptions["sync"]);
|
||||||
|
updateStorageUsage();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// activate planner cache clear button (Report issue tab). Clears the
|
||||||
|
// cached planner items locally, then tells any open Canvas tabs to
|
||||||
|
// re-fetch fresh data so the clear takes effect without a refresh.
|
||||||
|
// Keep the key in sync with PLANNER_CACHE_KEY in content.js.
|
||||||
|
document.querySelector("#planner-cache-clear-btn").addEventListener("click", async () => {
|
||||||
|
displayAlert(false, "Planner cache cleared.");
|
||||||
|
try {
|
||||||
|
await chrome.storage.local.remove("planner_cache_v1");
|
||||||
|
} catch (e) { /* nothing stored yet */ }
|
||||||
|
await sendFromPopup("clearPlannerCache");
|
||||||
|
updateStorageUsage();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---- Storage usage display (Report issue tab) ----
|
||||||
|
// Shows how much space the extension's settings, planner cache, and
|
||||||
|
// other local data take, plus the storage quota available to it.
|
||||||
|
function formatStorageBytes(bytes) {
|
||||||
|
if (bytes == null) return "unknown";
|
||||||
|
if (bytes < 1024) return bytes + " B";
|
||||||
|
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + " KB";
|
||||||
|
return (bytes / 1048576).toFixed(2) + " MB";
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBytesInUse is exact but not implemented everywhere (older Firefox),
|
||||||
|
// so fall back to measuring the serialized data as a Blob.
|
||||||
|
async function storageBytesInUse(area, keys) {
|
||||||
|
try {
|
||||||
|
const bytes = await area.getBytesInUse(keys ?? null);
|
||||||
|
if (typeof bytes === "number") return bytes;
|
||||||
|
} catch (e) { /* not supported */ }
|
||||||
|
try {
|
||||||
|
const data = await area.get(keys ?? null);
|
||||||
|
return new Blob([JSON.stringify(data)]).size;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateStorageUsage() {
|
||||||
|
const set = (id, text) => {
|
||||||
|
const el = document.querySelector(id);
|
||||||
|
if (el) el.textContent = text;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sync settings vs the 100 KB chrome.storage.sync quota.
|
||||||
|
const syncBytes = await storageBytesInUse(chrome.storage.sync);
|
||||||
|
set("#storage-usage-sync", syncBytes == null ? "unknown" : formatStorageBytes(syncBytes) + " of 100 KB quota");
|
||||||
|
|
||||||
|
// Planner cache: size plus item count, so users can see what the
|
||||||
|
// to-do cache actually costs (and that clearing it worked).
|
||||||
|
const cacheBytes = await storageBytesInUse(chrome.storage.local, "planner_cache_v1");
|
||||||
|
let cacheText = "empty";
|
||||||
|
if (cacheBytes) {
|
||||||
|
try {
|
||||||
|
const result = await chrome.storage.local.get("planner_cache_v1");
|
||||||
|
const count = result?.["planner_cache_v1"]?.items?.length;
|
||||||
|
cacheText = formatStorageBytes(cacheBytes) + (typeof count === "number" ? ` (${count} items)` : "");
|
||||||
|
} catch (e) {
|
||||||
|
cacheText = formatStorageBytes(cacheBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set("#storage-usage-cache", cacheText);
|
||||||
|
|
||||||
|
// Everything else in local storage (settings the extension keeps
|
||||||
|
// locally, state like the analytics toggle, etc.).
|
||||||
|
const localBytes = await storageBytesInUse(chrome.storage.local);
|
||||||
|
if (localBytes == null) {
|
||||||
|
set("#storage-usage-local", "unknown");
|
||||||
|
} else {
|
||||||
|
const other = cacheBytes != null ? Math.max(localBytes - cacheBytes, 0) : null;
|
||||||
|
set("#storage-usage-local", other == null ? formatStorageBytes(localBytes) : formatStorageBytes(other));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quota available to the extension. With the unlimitedStorage
|
||||||
|
// permission this is a large slice of free disk space, so it reads
|
||||||
|
// big — that is expected.
|
||||||
|
try {
|
||||||
|
const est = await navigator.storage.estimate();
|
||||||
|
if (est && est.quota) {
|
||||||
|
set("#storage-usage-quota", formatStorageBytes(est.quota) + " available");
|
||||||
|
}
|
||||||
|
} catch (e) { /* estimate unsupported */ }
|
||||||
|
}
|
||||||
|
updateStorageUsage();
|
||||||
|
|
||||||
// activate custom url input
|
// activate custom url input
|
||||||
document.querySelector('#customDomain').addEventListener('input', function () {
|
document.querySelector('#customDomain').addEventListener('input', function () {
|
||||||
let domains = this.value.split(",");
|
let domains = this.value.split(",");
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Canvas Refined",
|
"name": "Canvas Refined",
|
||||||
"description": "Even More Feature packed extension for Canvas.",
|
"description": "Even More Feature packed extension for Canvas.",
|
||||||
"version": "7.0.0",
|
"version": "7.0.1",
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "icon/icon-16.png",
|
"16": "icon/icon-16.png",
|
||||||
"32": "icon/icon-32.png",
|
"32": "icon/icon-32.png",
|
||||||
@ -30,7 +30,7 @@
|
|||||||
"run_at": "document_start"
|
"run_at": "document_start"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"permissions": ["storage"],
|
"permissions": ["storage", "unlimitedStorage"],
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user