How to initialize Chrome extension context menus under Manifest V3 service workers? [duplicate] - google-chrome-extension

I'm working on a simple link sharing extension (pinboard, readability, delicious, etc), and have a question about how to properly deal with a context menu item. In my non-persistent background page I call chrome.contextMenus.create and chrome.contextMenus.onClicked.addListener to setup/respond to the context menu.
The context menu entry works as expected. But the background page is showing the following error (right after it starts and before I've used the entry) :
contextMenus.create: Cannot create item with duplicate id id_share_link at chrome-extension://.../share.js:52:30 lastError:29 set
This made me realize that at no point do I remove the item or the listener. Knowing little about javascript and extensions, I'm left wondering if I'm doing everything correctly. I'm assuming this top-level code is going to re-execute every time the background page is invoked. So there are going to be redundant calls to create and addListener (and hence the error I see being logged).
I clearly can't do cleanup in response to suspend, as these calls need to be present to wake up the background script.
Should I be handling things differently?

If you want to use an event page, ie a non-persistent background page, as you call it, you should register a context menu via contextMenus.create in the event handler of runtime.onInstalled, as these context menu registrations ”persist“ anyways.
You have to add the listener-function for the contextMenus.onClicked event every time the event page gets reloaded, though, as the registration of your wish to listen on that event persists, while the handler callback itself does not. So generally don't call contextMenus.onClicked.addListener from runtime.onInstalled, but from top level or other code, that is guaranteed to be executed each time the event page loads.[1]

You can handle it one of two ways:
You can add the context menu and the listeners on install using:
chrome.runtime.onInstalled.addListener(function() {
/* Add context menu and listener */
});
You can remove the context menu and listener, and then re-add it each time the file is called.

[solution may no longer be the case, read comment]
runtime.onInstalled is not triggered if you disable/enable your extension.
My solution is to always add menu items and swallow errors:
'use strict';
{
let seqId = 0;
const createMenuLinkEntry = (title, tab2url) => {
const id = (++seqId).toString();
chrome.contextMenus.create({
id: id,
title: title,
contexts: ['browser_action'],
}, () => {
const err = chrome.runtime.lastError;
if(err) {
console.warn('Context menu error ignored:', err);
}
});
};
createMenuLinkEntry('Go to Google', (tab) => 'https://google.com');
createMenuLinkEntry('Go to GitHub', (tab) => 'https://github.com');
} // namespace

Related

Exit from express, nodejs programme on button click

I have created a file import functionality using nodejs,express and socket.io. Everything is working fine but now I have to implement a feature in which , user clicks on a button and on click of this button, I want to cancel/stop the execution of code (wherever the control is at that moment, should not execute further). I have tried return, res.end(), res.send(), eventEmitter, throw error etc but script processing is not getting stopped. On using process.exit, code execution stops but it shutdown for all users no matter from where they are operating(i.e if 5 users are using import feature, code executions stops for all five users). Can anybody have any other suggestions?
You can make an interval checking the if the state of the button (button click should change a flag) once you check the flag you can resolve your function ( the function should be a promise) and then it will stop the execution of the code example:
let flag = false
function test(){
return new Promise((res, rej)=>{
let interval = setInterval(()=>{
if (flag){
res();
}
},1000)
/*rest of code goes here*/
})
}

How to show a page on install but not on update [duplicate]

I have a question about chrome extension install/update event. If I add the onInstalled event listener in a top level code in the background script, is there a time frame in which my event listener will catch that event?
I'm asking this, because my demos showed that if I have some logic that executes before I hook onInstalled listener, it looks like it will never be executed, like that event happens in the meantime.
Can someone explain to me with more details how this event works, in the context of other logic in the background script, or point me to some documentation, since I haven't been able to find anything useful.
Thanks!
Update #Noam Hacker : Due to a company policy I can't post any real code here, but I have some pseudo code that illustrates my problem :
/**
* setup in which I miss onInstalled event
*/
function firstLogicThatRunsOnBackgroundLoad() {
// perform some logic
// perform some asynchronous operations via generators and promises
// which can take a while
chrome.runtime.onInstalled.addListener(function (details) {
if (details.reason == "install") {
// this logic never gets executed
} else if(details.reason == "update") {
// perform some logic
}
});
}
/**
* setup in which I catch onInstalled event
*/
function firstLogicThatRunsOnBackgroundLoad() {
chrome.runtime.onInstalled.addListener(function (details) {
if (details.reason == "install") {
// this logic executes
} else if(details.reason == "update") {
// perform some logic
}
});
// perform some logic
// perform some asynchronous operations via generators and promises
// which can take a while
}
onInstalled listeners catch events in these situations:
when the extension is first installed, when the extension is updated to a new version, and when Chrome is updated to a new version.
Since this is all asynchronous it will happen in the background, and according the documentation, fires immediately at any of these situations. Review asynchronous programming for some clarity on this.
link to documentation
According to your question it seems like you want help executing code in the right order. This answer provides a helpful framework for your case (using the reason attribute).
chrome.runtime.onInstalled.addListener(function(details){
if(details.reason == "install"){
//call a function to handle a first install
}else if(details.reason == "update"){
//call a function to handle an update
}
});
I needed to figure this out too. While I didn't find anything authoritative, I did throw a couple of console.time() statements in my background script.
Code was something like this:
console.time('onInstall event');
console.time('first function');
chrome.runtime.onInstalled.addListener(details => {
console.timeEnd('onInstall event');
});
// 7 module imports
someSyncFunction() // console.timeEnd('first function') is called in the first line in this function
Then I just loaded/reloaded the extension (unpacked, in dev mode) a few times. onInstall seems to pretty reliably fire within the first 50ms, while the first function happens w/in the first ms. Here are the results:
(First function, onInstall event)
(.282ms, 47.2ms)
(.331ms, 45.3ms)
(.327ms, 49.1ms)
(.294ms, 45.9ms)
Given that the document says
“Listeners must be registered synchronously from the start of the page.”
and
“Do not register listeners asynchronously, as they will not be properly triggered.”
, it seems they guarantee every synchronously-attached listener not to miss any, no matter how long it takes to evaluate your code. And this would be done by Chrome firing events after evaluating your entire code.
My hypothesis is that onInstalled actually works like onInitialized. No test data, though.

attaching Jquery on partially downloaded DOM [duplicate]

Essentially I want to have a script execute when the contents of a DIV change. Since the scripts are separate (content script in the Chrome extension & webpage script), I need a way simply observe changes in DOM state. I could set up polling but that seems sloppy.
For a long time, DOM3 mutation events were the best available solution, but they have been deprecated for performance reasons. DOM4 Mutation Observers are the replacement for deprecated DOM3 mutation events. They are currently implemented in modern browsers as MutationObserver (or as the vendor-prefixed WebKitMutationObserver in old versions of Chrome):
MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
var observer = new MutationObserver(function(mutations, observer) {
// fired when a mutation occurs
console.log(mutations, observer);
// ...
});
// define what element should be observed by the observer
// and what types of mutations trigger the callback
observer.observe(document, {
subtree: true,
attributes: true
//...
});
This example listens for DOM changes on document and its entire subtree, and it will fire on changes to element attributes as well as structural changes. The draft spec has a full list of valid mutation listener properties:
childList
Set to true if mutations to target's children are to be observed.
attributes
Set to true if mutations to target's attributes are to be observed.
characterData
Set to true if mutations to target's data are to be observed.
subtree
Set to true if mutations to not just target, but also target's descendants are to be observed.
attributeOldValue
Set to true if attributes is set to true and target's attribute value before the mutation needs to be recorded.
characterDataOldValue
Set to true if characterData is set to true and target's data before the mutation needs to be recorded.
attributeFilter
Set to a list of attribute local names (without namespace) if not all attribute mutations need to be observed.
(This list is current as of April 2014; you may check the specification for any changes.)
Edit
This answer is now deprecated. See the answer by apsillers.
Since this is for a Chrome extension, you might as well use the standard DOM event - DOMSubtreeModified. See the support for this event across browsers. It has been supported in Chrome since 1.0.
$("#someDiv").bind("DOMSubtreeModified", function() {
alert("tree changed");
});
See a working example here.
Many sites use AJAX/XHR/fetch to add, show, modify content dynamically and window.history API instead of in-site navigation so current URL is changed programmatically. Such sites are called SPA, short for Single Page Application.
Usual JS methods of detecting page changes
MutationObserver (docs) to literally detect DOM changes.
Info/examples:
How to change the HTML content as it's loading on the page
Performance of MutationObserver to detect nodes in entire DOM.
Lightweight observer to react to a change only if URL also changed:
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
onUrlChange();
}
}).observe(document, {subtree: true, childList: true});
function onUrlChange() {
console.log('URL changed!', location.href);
}
Event listener for sites that signal content change by sending a DOM event:
pjax:end on document used by many pjax-based sites e.g. GitHub,
see How to run jQuery before and after a pjax load?
message on window used by e.g. Google search in Chrome browser,
see Chrome extension detect Google search refresh
yt-navigate-finish used by Youtube,
see How to detect page navigation on YouTube and modify its appearance seamlessly?
Periodic checking of DOM via setInterval:
Obviously this will work only in cases when you wait for a specific element identified by its id/selector to appear, and it won't let you universally detect new dynamically added content unless you invent some kind of fingerprinting the existing contents.
Cloaking History API:
let _pushState = History.prototype.pushState;
History.prototype.pushState = function (state, title, url) {
_pushState.call(this, state, title, url);
console.log('URL changed', url)
};
Listening to hashchange, popstate events:
window.addEventListener('hashchange', e => {
console.log('URL hash changed', e);
doSomething();
});
window.addEventListener('popstate', e => {
console.log('State changed', e);
doSomething();
});
P.S. All these methods can be used in a WebExtension's content script. It's because the case we're looking at is where the URL was changed via history.pushState or replaceState so the page itself remained the same with the same content script environment.
Another approach depending on how you are changing the div.
If you are using JQuery to change a div's contents with its html() method, you can extend that method and call a registration function each time you put html into a div.
(function( $, oldHtmlMethod ){
// Override the core html method in the jQuery object.
$.fn.html = function(){
// Execute the original HTML method using the
// augmented arguments collection.
var results = oldHtmlMethod.apply( this, arguments );
com.invisibility.elements.findAndRegisterElements(this);
return results;
};
})( jQuery, jQuery.fn.html );
We just intercept the calls to html(), call a registration function with this, which in the context refers to the target element getting new content, then we pass on the call to the original jquery.html() function. Remember to return the results of the original html() method, because JQuery expects it for method chaining.
For more info on method overriding and extension, check out http://www.bennadel.com/blog/2009-Using-Self-Executing-Function-Arguments-To-Override-Core-jQuery-Methods.htm, which is where I cribbed the closure function. Also check out the plugins tutorial at JQuery's site.
In addition to the "raw" tools provided by MutationObserver API, there exist "convenience" libraries to work with DOM mutations.
Consider: MutationObserver represents each DOM change in terms of subtrees. So if you're, for instance, waiting for a certain element to be inserted, it may be deep inside the children of mutations.mutation[i].addedNodes[j].
Another problem is when your own code, in reaction to mutations, changes DOM - you often want to filter it out.
A good convenience library that solves such problems is mutation-summary (disclaimer: I'm not the author, just a satisfied user), which enables you to specify queries of what you're interested in, and get exactly that.
Basic usage example from the docs:
var observer = new MutationSummary({
callback: updateWidgets,
queries: [{
element: '[data-widget]'
}]
});
function updateWidgets(summaries) {
var widgetSummary = summaries[0];
widgetSummary.added.forEach(buildNewWidget);
widgetSummary.removed.forEach(cleanupExistingWidget);
}

Persistent background page on demand or an event page that doesn't unload?

I want to build a extension that behaves like a timer. It should count down the seconds when activated, but should do nothing with inactive.
The chrome.alarms API is interesting, but does not have enough precision nor granularity. It only fires at most once per minute, and it may fire late. If I want something to execute more often than that, I can't use this API.
Then, the next natural solution is to use a background page and use setTimeout or setInterval in there. However, background pages are persistent, and they take up resources (e.g. memory) even when idle. So they are not ideal.
The best solution seems to be an event page to run the timer. However, the documentation says:
Once it has been loaded, the event page will stay running as long as it is active (for example, calling an extension API or issuing a network request).
[…]
Once the event page has been idle a short time (a few seconds), the runtime.onSuspend event is dispatched. The event page has a few more seconds to handle this event before it is forcibly unloaded.
[…]
If your extension uses window.setTimeout() or window.setInterval(), switch to using the alarms API instead. DOM-based timers won't be honored if the event page shuts down.
Unfortunately, having an active setInterval is not enough to consider an event page active. In fact, from my tests, an interval up to 10 seconds is short enough to keep the event page running, but anything greater than 10 or 15 seconds is too far apart and the event page will get unloaded. I've tested this on my crx-reload-tab project.
I believe what I want is a middle ground:
I want a background page that I can load and unload on demand. (Instead of one that keeps loaded all the time.)
I want an event page that stays persistent in memory for as long as I say; but otherwise could be unloaded. (Instead of one that gets unloaded automatically by the browser.)
Is it possible? How can I do it?
Background pages cannot be unloaded on demand, and Chrome decides Event page lifecycle for you (there is nothing you can do in onSuspend to prevent it).
If your concern is timers, you could try my solution from this answer, which basically splits a timer into shorter timers for a "sparse" busy-wait. That's enough to keep the event page loaded and is a viable solution if you don't need to do that frequently.
In general, there are some things that will keep an event page loaded:
If you're using message passing, be sure to close unused message ports. The event page will not shut down until all message ports are closed.
This can be exploited if you have any other context to keep an open Port to, for example a content script. See Long-lived connections docs for more details.
In practice, if you often or constantly need precise, sub-minute timers, an Event page is a bad solution. Your resource gains from using one might not justify it.
As mentioned in Xan's answer we can abuse messaging. There's nothing wrong about it either in case you want to temporarily prevent the event page from unloading. For example while displaying a progress meter using chrome.notifications API or any other activity based on setTimeout/setInterval that may exceed the default unload timeout which is 5-15 seconds.
Demo
It creates an iframe in the background page and the iframe connects to the background page. In addition to manifest.json and a background script you'll need to make two additional files bg-iframe.html and bg-iframe.js with the code specified below.
manifest.json excerpt:
"background": {
"scripts": ["bg.js"],
"persistent": false
}
bg.js:
function preventUnload() {
let iframe = document.querySelector('iframe');
if (!iframe) {
iframe = document.createElement('iframe');
document.body.appendChild(iframe).src = 'bg-iframe.html';
}
}
function allowUnload() {
let iframe = document.querySelector('iframe');
if (iframe) iframe.remove();
}
chrome.runtime.onConnect.addListener(() => {});
bg-iframe.html:
<script src="bg-iframe.js"></script>
bg-iframe.js:
chrome.runtime.connect();
Usage example in bg.js:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message === 'start') doSomething();
});
function doSomething() {
preventUnload();
// do something asynchronous that's spread over time
// like for example consecutive setTimeout or setInterval calls
let ticks = 20;
const interval = setInterval(tick, 1000);
function tick() {
// do something
// ................
if (--ticks <= 0) done();
}
function done() {
clearInterval(interval);
allowUnload();
}
}
I use this function:
function _doNotSleep() {
if (isActive) {
setTimeout(() => {
fetch(chrome.runtime.getURL('manifest.json'));
_doNotSleep();
}, 2000);
}
}
But the problem with such approach is that Devtools network tab polluted with this http stub.

view events are set twice when I instantiate view second time

This is my project architecture issue - its my first backbone project and I probably did something wrong.
In all my project in route callbacks I have:
myroute: function() {
this.currentView = new MyCustomView();
},
mysecondroute: function() {
this.currentView = new MySecondView()
},
//...
So in all route callbacks I instantiate some view. This view has initialze method which calls render method. It works except that all view events (declared in events: {}) are 'binded' every time same view is instantiated. so when I visit same route twice events for view corresponding to this route are fired twice...
Probably I shouldn instantiate new view on every route call - but how I can do this ? I mean what are the standards? Maybe I should just unload current view somehow - is there any method to do this?
I think you have to add a method to unbind all the events at the time to close the view
like this
close : function () {
//your code to clean everything before closing the view
this.remove();
this.unbind();
}
so the next time the view is called the events will be added during the initialization of the view, thats why you had events being called twice. the initialize method binds the events to the .el element. you need to make sure you unbind those at some point.

Resources