Ответ 1
ОБНОВЛЕНИЕ (2016-05-24):
Интересный факт: все это тайное хакерство больше не требуется; см. новый флаг
requireInteraction
Это доступно с Chrome 50. Дополнительная информация.
Благодаря комментарию root этот ответ пересматривается с учетом того факта, что событие onClosed
не запускается, когда уведомление исчезает (в области уведомлений) через несколько секунд. Это все-таки хакерское решение.
Фон
Вы можете воспользоваться тем фактом, что жизненный цикл уведомления заканчивается одним из следующих событий:
-
onClosed
: когда пользователь нажимает на маленький "х" в верхнем правом углу. -
onClicked
: когда пользователь нажимает на тело сообщения (а не "x", а не некоторые кнопки). -
onButtonClicked
: когда пользователь нажимает на одну из кнопок (если есть).
Решение
Предлагаемое решение состоит из следующих шагов:
- Зарегистрируйте слушателей для всех событий, упомянутых выше.
- Зарегистрируйте тайм-аут через несколько секунд (например, 30) - после того, как уведомление будет скрыто - это будет удалить и create уведомление (поэтому оно эффективно остается видимым на экране.
- Если какой-либо из слушателей, установленный на шаге 1, срабатывает, это означает, что пользователь занесен с уведомлением, поэтому отмените тайм-аут (вам не нужно повторно создавать уведомление).
Писать об этом просто, кодирование требует еще больших усилий:) Вот пример кода, который я использовал для достижения описанного выше:
В manifest.json:
{
"manifest_version": 2,
"name": "Test Extension",
"version": "0.0",
"background": {
// We need this for the `Timeout` - see notes below
"persistent": true,
"scripts": ["background.js"]
},
"browser_action": {
"default_title": "Test Extension"
"default_icon": {
"19": "img/icon19.png",
"38": "img/icon38.png"
},
},
"permissions": ["notifications"]
}
В background.js:
var pendingNotifications = {};
/* For demonstration purposes, the notification creation
* is attached to the browser-action `onClicked` event.
* Change according to your needs. */
chrome.browserAction.onClicked.addListener(function() {
var dateStr = new Date().toUTCString();
var details = {
type: "basic",
iconUrl: "/img/notifIcon.png",
title: "REMINDER",
message: dateStr + "\n\n"
+ "There is one very important matter to attend to !\n"
+ "Deal with it now ?",
contextMessage: "Very important stuff...",
buttons: [
{ title: "Yes" },
{ title: "No" }
]
};
var listeners = {
onButtonClicked: function(btnIdx) {
if (btnIdx === 0) {
console.log(dateStr + ' - Clicked: "yes"');
} else if (btnIdx === 1) {
console.log(dateStr + ' - Clicked: "no"');
}
},
onClicked: function() {
console.log(dateStr + ' - Clicked: "message-body"');
},
onClosed: function(byUser) {
console.log(dateStr + ' - Closed: '
+ (byUser ? 'by user' : 'automagically (!?)'));
}
};
/* Create the notification */
createNotification(details, listeners);
});
/* Create a notification and store references
* of its "re-spawn" timer and event-listeners */
function createNotification(details, listeners, notifId) {
(notifId !== undefined) || (notifId = "");
chrome.notifications.create(notifId, details, function(id) {
console.log('Created notification "' + id + '" !');
if (pendingNotifications[id] !== undefined) {
clearTimeout(pendingNotifications[id].timer);
}
pendingNotifications[id] = {
listeners: listeners,
timer: setTimeout(function() {
console.log('Re-spawning notification "' + id + '"...');
destroyNotification(id, function(wasCleared) {
if (wasCleared) {
createNotification(details, listeners, id);
}
});
}, 10000)
};
});
}
/* Completely remove a notification, cancelling its "re-spawn" timer (if any)
* Optionally, supply it with a callback to execute upon successful removal */
function destroyNotification(notifId, callback) {
/* Cancel the "re-spawn" timer (if any) */
if (pendingNotifications[notifId] !== undefined) {
clearTimeout(pendingNotifications[notifId].timer);
delete(pendingNotifications[notifId]);
}
/* Remove the notification itself */
chrome.notifications.clear(notifId, function(wasCleared) {
console.log('Destroyed notification "' + notifId + '" !');
/* Execute the callback (if any) */
callback && callback(wasCleared);
});
}
/* Respond to the user clicking one of the buttons */
chrome.notifications.onButtonClicked.addListener(function(notifId, btnIdx) {
if (pendingNotifications[notifId] !== undefined) {
var handler = pendingNotifications[notifId].listeners.onButtonClicked;
destroyNotification(notifId, handler(btnIdx));
}
});
/* Respond to the user clicking on the notification message-body */
chrome.notifications.onClicked.addListener(function(notifId) {
if (pendingNotifications[notifId] !== undefined) {
var handler = pendingNotifications[notifId].listeners.onClicked;
destroyNotification(notifId, handler());
}
});
/* Respond to the user clicking on the small 'x' in the top right corner */
chrome.notifications.onClosed.addListener(function(notifId, byUser) {
if (pendingNotifications[notifId] !== undefined) {
var handler = pendingNotifications[notifId].listeners.onClosed;
destroyNotification(notifId, handler(byUser));
}
});
Заключительные примечания:
- Если ваши уведомления чрезвычайно важны, вам следует реализовать механизм "восстановления", например, в случае сбоя ОС ОС или внезапного завершения. Например. опираясь на более постоянное хранилище (
localStorage
,chrome.storage
API и т.д.), возобновление ожидающих уведомлений при запуске/запуске браузера и т.д. Суб > - Это может быть хорошей идеей установить ограничение на общее количество ожидающих уведомлений, для "удобства удобства". Если ваши ожидающие уведомления превышают, скажем, 3 в любой момент, вы можете заменить их на тот, который сообщает, что ожидающие уведомления и направляют пользователя на страницу, где вы перечисляете все из них. (Код будет значительно сложнее, но эй ничего для пользователя, правильно?;)
- Вместо того чтобы пытаться сохранять уведомления на экране до тех пор, пока пользователь не решит иметь дело с ними, лучше было бы использовать Badge (который может иметь цвет и небольшой текст, указывая количество ожидающих уведомлений.
- Я не рассматривал его, но возможно (и в этом случае также целесообразно) заменить
Timeout
наchrome.alarms
API, а затем преобразовать фоновую страницу в непостоянную (aka event-страница), что сделает ее более дружественной к ресурсам.