Skip to content

Commit df75fe1

Browse files
committed
Feat(memopt): add memory optimization function
1 parent 36e3e8c commit df75fe1

File tree

11 files changed

+339
-6
lines changed

11 files changed

+339
-6
lines changed

_locales/en/messages.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,5 +426,14 @@
426426
},
427427
"popupMoreSubtitle": {
428428
"message": "Click to go to detailed settings"
429+
},
430+
"memoryOptimized": {
431+
"message": "Memory usage has been optimized"
432+
},
433+
"memoryOptimizedSub": {
434+
"message": "Cloudopt Adblocker intelligently optimizes tabs that have not been accessed for a long time and releases memory"
435+
},
436+
"memoryOptimizedReturn": {
437+
"message": "Resume Now"
429438
}
430439
}

_locales/ko/messages.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,5 +426,14 @@
426426
},
427427
"popupMoreSubtitle": {
428428
"message": "자세한 설정을 보려면 설정 페이지로 이동하십시오."
429+
},
430+
"memoryOptimized": {
431+
"message": "메모리 사용이 최적화되었습니다"
432+
},
433+
"memoryOptimizedSub": {
434+
"message": "Cloudopt Adblocker는 오랫동안 액세스하지 않은 탭을 지능적으로 최적화하고 메모리를 해제합니다."
435+
},
436+
"memoryOptimizedReturn": {
437+
"message": "지금 재개"
429438
}
430439
}

_locales/zh_CN/messages.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,5 +426,14 @@
426426
},
427427
"popupMoreSubtitle": {
428428
"message": "点击前往设置页面进行详细设置"
429+
},
430+
"memoryOptimized": {
431+
"message": "内存占用已被优化"
432+
},
433+
"memoryOptimizedSub": {
434+
"message": "Cloudopt Adblocker 会智能优化长时间未被访问的标签页并释放内存"
435+
},
436+
"memoryOptimizedReturn": {
437+
"message": "立即恢复"
429438
}
430439
}

_locales/zh_TW/messages.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,5 +426,14 @@
426426
},
427427
"popupMoreSubtitle": {
428428
"message": "点击前往设置页面进行详细设置"
429+
},
430+
"memoryOptimized": {
431+
"message": "內存佔用已被優化"
432+
},
433+
"memoryOptimizedSub": {
434+
"message": "Cloudopt Adblocker 會智能優化長時間未被訪問的標籤頁並釋放內存"
435+
},
436+
"memoryOptimizedReturn": {
437+
"message": "立即恢復"
429438
}
430439
}

image/md-icon-pause.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import * as logger from '../core/logger'
2+
import * as utils from '../core/utils'
3+
import * as message from './message'
4+
import * as coreConfig from '../core/config'
5+
6+
function memoryOptimization() {
7+
let idleSince = {};
8+
let ticktock = null;
9+
let currentActivated = 0;
10+
let suspendedUrls = {};
11+
let streamTills = {};
12+
13+
// Notion: Code to send to tabs, MUST NOT invoke directly.
14+
function getherPageInfo() {
15+
let links = document.getElementsByTagName("link");
16+
let favicon = "";
17+
for (let i = 0; i < links.length; i++) {
18+
let rel = links[i].getAttribute('rel').toLowerCase();
19+
if (rel === "icon" || rel === "shortcut icon") {
20+
favicon = links[i].href;
21+
break;
22+
}
23+
}
24+
25+
return {
26+
formChanged: Boolean(window._co_cloudopt_formChanged),
27+
title: document.title,
28+
scrollTop: window.scrollY || window.scrollTop || document.getElementsByTagName("html")[0].scrollTop,
29+
url: window.location.href,
30+
favicon
31+
};
32+
}
33+
function suspend(params) {
34+
let suspendUrl = window._co_cloudopt_getExtUrl() + "/suspend.html?p=" + params;
35+
window.location.replace(suspendUrl);
36+
}
37+
function windowScroll(pos) {
38+
if (pos > 0) {
39+
window.scrollTop = document.body.scrollTop = document.documentElement.scrollTop = pos;
40+
}
41+
}
42+
// End of code to send to tabs
43+
const getherPageInfoCode = "(" + getherPageInfo.toString() + ")();";
44+
const suspendCode = "(" + suspend.toString() + ")";
45+
const windowScrollCode = "(" + windowScroll.toString() + ")";
46+
47+
function checkForMemOpt() {
48+
chrome.tabs.query(
49+
{
50+
active: false,
51+
pinned: false,
52+
audible: false,
53+
url: "<all_urls>",
54+
status: "complete"
55+
},
56+
function (tabs) {
57+
let currentTime = Date.now();
58+
tabs.filter(function (tab) {
59+
return idleSince[tab.id] && currentTime - idleSince[tab.id] > 1800000 // Don't hangup a tab that've been activated in last 30min
60+
}).forEach(function (tab) {
61+
chrome.tabs.executeScript(tab.id, { code: getherPageInfoCode }, function (results) {
62+
if (chrome.runtime.lastError) {
63+
logger.debug(chrome.runtime.lastError.message);
64+
return;
65+
}
66+
if (!results) {
67+
logger.debug("Error while executing getherPageInfoCode: no result");
68+
return;
69+
}
70+
71+
// Number of reasons not to hang up the tab
72+
let len = results.filter(function (item) {
73+
if (!item)
74+
return false;
75+
if (item.formChanged) // Form input edited
76+
return true;
77+
if (streamTills[tab.id] && currentTime - streamTills[tab.id] < 300000) // Audio/Video requests in last 5min
78+
return true;
79+
return false;
80+
}).length;
81+
if (len > 0) {
82+
return;
83+
}
84+
85+
let pageInfo = results[0];
86+
let params = {
87+
t: pageInfo ? pageInfo.title : "Suspended Page",
88+
s: pageInfo ? pageInfo.scrollTop : 0,
89+
u: pageInfo ? pageInfo.url : tab.url,
90+
i: pageInfo ? pageInfo.favicon : ""
91+
}
92+
93+
let code = suspendCode + '("' + btoa(encodeURIComponent(JSON.stringify(params))) + '");';
94+
95+
chrome.tabs.executeScript(tab.id, { code });
96+
if (chrome.runtime.lastError) {
97+
logger.debug(chrome.runtime.lastError.message);
98+
}
99+
});
100+
});
101+
}
102+
);
103+
};
104+
105+
function preCheckForMemOpt() {
106+
chrome.system.memory.getInfo(function (info) {
107+
if (info.availableCapacity / info.capacity < 0.2) {
108+
checkForMemOpt();
109+
}
110+
});
111+
}
112+
113+
function saveSuspendUrl(tab) {
114+
try {
115+
let paramStr = (new URL(tab.url)).searchParams.get('p');
116+
let paramObj = JSON.parse(decodeURIComponent(atob(paramStr)));
117+
118+
suspendedUrls[tab.id] = {
119+
url: paramObj.u,
120+
scrollTop: paramObj.s
121+
};
122+
} catch (e) {
123+
// Params are broken, ignore them
124+
// No operation here
125+
}
126+
}
127+
128+
function enable() {
129+
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
130+
if (changeInfo.status == "complete") {
131+
if (!tab.active) {
132+
idleSince[tabId] = Date.now();
133+
return;
134+
}
135+
if (suspendedUrls[tab.id] && suspendedUrls[tab.id].url === tab.url) {
136+
let code = windowScrollCode + '(' + suspendedUrls[tab.id].scrollTop + ');';
137+
chrome.tabs.executeScript(tab.id, { code });
138+
if (chrome.runtime.lastError) {
139+
logger.debug(chrome.runtime.lastError.message);
140+
}
141+
delete suspendedUrls[tab.id];
142+
}
143+
if (streamTills[tab.id]) {
144+
delete streamTills[tab.id];
145+
}
146+
}
147+
});
148+
chrome.tabs.onActivated.addListener(function (activeInfo) {
149+
if (activeInfo.tabId === currentActivated) {
150+
return;
151+
}
152+
let lastActivated = currentActivated;
153+
currentActivated = activeInfo.tabId;
154+
155+
if (lastActivated) {
156+
idleSince[lastActivated] = Date.now();
157+
}
158+
159+
chrome.tabs.get(currentActivated, function (tab) {
160+
if (chrome.runtime.lastError) {
161+
logger.debug(chrome.runtime.lastError.message);
162+
return;
163+
}
164+
if (tab.url.startsWith(utils.getExtUrl() + "/suspend.html")) {
165+
saveSuspendUrl(tab);
166+
}
167+
});
168+
});
169+
chrome.tabs.onRemoved.addListener(function (tabId) {
170+
if (idleSince[tabId]) {
171+
delete idleSince[tabId];
172+
}
173+
if (suspendedUrls[tabId]) {
174+
delete suspendedUrls[tabId];
175+
}
176+
if (streamTills[tabId]) {
177+
delete streamTills[tabId];
178+
}
179+
});
180+
chrome.webRequest.onHeadersReceived.addListener(function (details) {
181+
let len = details.responseHeaders.filter(function (item) {
182+
return item.name.toLowerCase() === "content-type" && (item.value.startsWith("audio") || item.value.startsWith("video") || item.value.indexOf("stream") >= 0);
183+
}).length;
184+
if (len > 0) {
185+
streamTills[details.tabId] = Date.now();
186+
}
187+
}, { urls: ["<all_urls>"] }, ["responseHeaders"]);
188+
ticktock = setInterval(preCheckForMemOpt, 60000)
189+
}
190+
191+
async function refreshConfig() {
192+
const config = await coreConfig.get()
193+
if (config.memoryOptimize) {
194+
if (!ticktock) {
195+
enable();
196+
}
197+
} else {
198+
if (ticktock) {
199+
clearInterval(ticktock);
200+
ticktock = null;
201+
};
202+
}
203+
}
204+
205+
message.addListener({
206+
type: "refresh-config", callback(message, sender, sendResponse) {
207+
refreshConfig();
208+
sendResponse("");
209+
}
210+
});
211+
212+
refreshConfig();
213+
}
214+
215+
$.ready.then(memoryOptimization)

src/content/memopt.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
/* tslint:disable:interface-name */
2-
interface Window {
3-
_co_cloudopt_formChanged?: boolean
4-
}
5-
/* tslint:enable:interface-name */
1+
import * as utils from '../core/utils'
62

73
window.addEventListener('keydown', (event) => {
84
function isPrintable(keyCode: number) {
@@ -25,3 +21,5 @@ window.addEventListener('keydown', (event) => {
2521
}
2622
/* tslint:enable:no-string-literal */
2723
})
24+
25+
window._co_cloudopt_getExtUrl = utils.getExtUrl

src/core/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ declare global {
1515
interface Window {
1616
adguardApi?: any
1717
_cloudopt_accelerated?: boolean
18+
scrollTop?: number
19+
_co_cloudopt_formChanged?: boolean
20+
_co_cloudopt_getExtUrl?: Function
1821
}
1922
interface Navigator {
2023
browserLanguage?: string
@@ -173,3 +176,7 @@ export function deserializeMapNumNum(recordString: string): Map<number, number>
173176
})
174177
return records
175178
}
179+
180+
export function getExtUrl() {
181+
return chrome.runtime.getURL("popup.html").replace("/popup.html", "")
182+
}

src/suspend/suspend.html

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<text i18n="memoryOptimizedIcon" placeholder=""></text>
6+
<script src="lib/jquery.min.js"></script>
7+
</head>
8+
9+
<body>
10+
<div class="container">
11+
<div class="content">
12+
<div class="icon"></div>
13+
<p class="content-text" i18n="memoryOptimized">内存占用已被优化</p>
14+
<p class="content-subtext" i18n="memoryOptimizedSub">Cloudopt Adblocker 会智能优化长时间未被访问的标签页并释放内存</p>
15+
<button id="return" class="btn" i18n="memoryOptimizedReturn">立即恢复</button>
16+
</div>
17+
</div>
18+
<style type="text/css">
19+
.container {
20+
display: flex;
21+
height: 100vh;
22+
justify-content: center;
23+
text-align: center;
24+
}
25+
26+
.content {
27+
align-self: center;
28+
}
29+
30+
.content-text {
31+
color: #56616a;
32+
font-size: 22px;
33+
line-height: 20px;
34+
}
35+
36+
.content-subtext {
37+
font-size: 13px;
38+
color: #747f8e;
39+
text-align: center;
40+
line-height: 20px;
41+
margin: 0;
42+
}
43+
44+
div.icon {
45+
mask: url(image/md-icon-pause.svg) no-repeat;
46+
-webkit-mask: url(image/md-icon-pause.svg) no-repeat;
47+
mask-size: cover;
48+
-webkit-mask-size: cover;
49+
background-color: #d9534f;
50+
height: 60px;
51+
width: 68px;
52+
display: inline-block;
53+
}
54+
55+
.btn {
56+
border: 0;
57+
border-radius: 5rem;
58+
color: white;
59+
background-color: #3333CC;
60+
width: 210px;
61+
height: 45px;
62+
cursor: pointer;
63+
margin-top: 15px;
64+
}
65+
</style>
66+
</body>
67+
68+
</html>
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import * as utils from '../core/utils'
2+
import * as i18n from '../core/i18n'
23
import $ from 'jquery'
34

45
$(document).ready(() => {
5-
6+
i18n.translateCurrentPage()
67
try {
78
const paramStr = utils.getQueryString('p')
89
const paramObj = JSON.parse(decodeURIComponent(atob(paramStr)))

0 commit comments

Comments
 (0)