I'd like to prevent users from right-clicking on a Chromium instance generated in Puppeteer. How can I do this?
The most efficient way to do it is to register a function that will run before opening any new page:
await page.evaluateOnNewDocument(() =>
document.addEventListener('contextmenu', event => event.preventDefault())
);
Of course the target page could remove and/or reassign that event listener. So another way is to wait for the page to load (or wait for some selector) and create event listener again:
await page.evaluate(() =>
document.addEventListener('contextmenu', event => event.preventDefault())
);
Or you could do an even more devious thing and hijack document.addEventListener to disallow adding contextmenu event listeners at all:
await page.evaluateOnNewDocument(() => {
document.addEventListener('contextmenu', event => event.preventDefault())
const realAddEventListener = document.addEventListener;
document.addEventListener = function(type, listener){
type === 'contextmenu' || realAddEventListener.apply(this, arguments);
};
}
);
(based on this answer and its comment)
Related
Today I ran into rather a strange behaviour of Chrome. I was playing with PerformanceObserver and found out that when you add two stylesheets with the same URL to the DOM very quickly then chrome fires only one request which obviously makes sense as it saves network load.
const testCase = async () => {
let numberOfRecords = 0
const observer = new PerformanceObserver((entryList) => {
const performanceEntries = entryList.getEntries()
numberOfRecords += performanceEntries.length
})
observer.observe({ entryTypes: ['resource'] })
// Test: Only one performance record is created because links are added at the same time
// and chrome detects duplicate request
const linkElement1 = document.createElement('link')
linkElement1.rel = 'stylesheet'
linkElement1.href = 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css'
document.head.appendChild(linkElement1)
const linkElement2 = document.createElement('link')
linkElement2.rel = 'stylesheet'
linkElement2.href = 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css'
document.head.appendChild(linkElement2)
// wait a little bit so performance observer callback is called
await new Promise((resolve) => {
setTimeout(() => resolve(), 1000)
})
console.assert(numberOfRecords === 1, 'Test')
console.log('Test finished')
}
testCase()
When sleep time is added between adding link nodes to DOM then chrome fires two requests (the second one is taken from cache)
const testCase = async () => {
let numberOfRecords = 0
const observer = new PerformanceObserver((entryList) => {
const performanceEntries = entryList.getEntries()
numberOfRecords += performanceEntries.length
})
observer.observe({ entryTypes: ['resource'] })
// Test: Only one performance record is created because links are added at the same time
// and chrome detects duplicate request
const linkElement1 = document.createElement('link')
linkElement1.rel = 'stylesheet'
linkElement1.href = 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css'
document.head.appendChild(linkElement1)
// wait here so chrome triggers two requests
await new Promise((resolve) => {
setTimeout(() => resolve(), 1000)
})
const linkElement2 = document.createElement('link')
linkElement2.rel = 'stylesheet'
linkElement2.href = 'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css'
document.head.appendChild(linkElement2)
// wait a little bit so performance observer callback is called
await new Promise((resolve) => {
setTimeout(() => resolve(), 1000)
})
console.assert(numberOfRecords === 2, 'Test')
console.log('Test finished')
}
testCase()
However when I run this second code via automated test (webdriver.io) or I try it on cloud service like Browserstack/Lambdatest (the same browser version, OS version) it fails as it triggers only one request. So I wonder what's the difference?
To see it by yourself you can open some empty page (it's quite important that page is empty and doesn't contain any background requests) and copy the code examples to console.
I just wonder whether you disabled the cache for the automation tests. In Chrome, apparently if you didn't tick "Disable cache", then you second test should fail.
I created a countdown from 5 to 0. It start when you click on the "START" button:
<html>
<head>
<meta charset="utf-8"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js">
</script>
</head>
<body>
<button id="start">START</button>
COUNTDOWN:<span id="countdown"></span>
<script>
let start = document.getElementById('start');
let start_click = rxjs.fromEvent(start, 'click');
start_click.subscribe(x => console.log('click'));
start_click.pipe(rxjs.operators.first()).subscribe(
()=> {
let time = rxjs.timer(0, 1000).pipe(
rxjs.operators.skip(0)
, rxjs.operators.take(6)
, rxjs.operators.map(x => 5-x)
);
time.subscribe(x => console.log('instant', x));
let countdown = document.getElementById('countdown');
time.subscribe(x => countdown.innerText = x);
start.disabled = true;
let end = time.pipe(
rxjs.operators.last()
, rxjs.operators.repeatWhen(() => start_click)
);
end.subscribe(x=>start.disabled = false);
start_click.subscribe(x => start.disabled = true);
});
</script>
</body>
</html>
I struggle to find how to reset the countdown when the "START" button is pressed again.
I tried to add:
start_click.subscribe(x => countdown.innerText = 5);
But the value is static. Thanks.
The reason it doesn't work after clicking 'Start' a second time is because you are using the first() operator on your start_click observable.
This means the observable only emits on the first click, then completes.
Simply remove .pipe(rxjs.operators.first()) and your code will work each time you click the button.
However, it's generally a good idea to avoid nested subscriptions when possible. This can help you avoid memory leaks (due not unsubscribing properly) and make the code easier to understand.
You can avoid using nested subscriptions by using one of the "Higher Order Mapping Operators" LINK. This is just a fancy way of saying: operators that map the incoming value to another observable, subscribe to it, and emit those values. They also manage these "inner subscriptions" automatically.
The switchMap operator will "switch" to a new observable whenever a new value is received. So in your case, whenever a new click is received, a new 5-second timer observable is created.
Simplified code could look something like this: Working StackBlitz
const start = document.getElementById('start');
const countdown = document.getElementById('countdown');
const start_click = rxjs.fromEvent(start, 'click');
const time = start_click.pipe(
tap(() => start.disabled = true),
switchMap(() => timer(0, 1000).pipe(
map(x => 5-x),
take(6),
finalize(() => start.disabled = false)
)),
);
time.subscribe(
x => countdown.innerText = x
);
Notice how there is only a single subscription now. We defined two different observables, start_click which is your stream of clicks and time which is your stream that emits the current value of the timer. time is defined from the start_click stream, so whenever a new click is received, under the hood a new timer gets created and emits values.
The issue is caused by rxjs.operators.first(),
try next
start_click.pipe(
rxjs.operators.tap(() => start.disabled = true)
, switchMap(() => rxjs.timer(0, 1000).pipe(
rxjs.operators.skip(0) // <- do you need it?
, rxjs.operators.take(6)
, rxjs.operators.map(x => 5-x)
, rxjs.operators.finalize(() => start.disabled = false)
)),
).subscribe(x => {
console.log('instant', x);
let countdown = document.getElementById('countdown');
countdown.innerText = x;
});
In app use content script for all pages, and send message to active page on complete loaded page, but I have many calls of script sometimes 2 and more:
You can see that here
Code implimentation:
chrome.tabs.onCreated.addListener(function (tabs) {
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
if(changeInfo.status === "complete") {
let tabid = tab.id;
console.log("Site is valid: url -> " + tab.url)
chrome.tabs.executeScript(tab.id, {
file: '/injections/mobile.bet365.com.js',
});
console.log(tab);
setTimeout(function () {
console.log("timeout was set")
chrome.tabs.query({}, function (tabs) {
let countOpenedTabsFrom = tabs.length;
let opener = 1;
// на целевой вкладке
chrome.tabs.sendMessage(tabid, {
message: "start_app",
opener: opener,
queuenumber: countOpenedTabsFrom
}, function (response) {
console.log(response);
});
});
}, 500);
}
And executed script have many queries too.
Why is this happen?
Every time onCreated event fires, you're adding a new onUpdated listener.
When, after that, onUpdated event fires, all of them are executed, leading to the behavior you're seeing.
You either need to de-register the handlers when they are done, or register the handler only once. See chrome.events docs (which describe common points of all event objects in other APIs) for ideas on how to implement that.
Note that the code inside chrome.tabs.onCreated listener does not use the tabs parameter at all, so it's not clear why do you even need to listen to onCreated.
I have an html button that when pressed, calls a function that sets a 'display' boolean to true which makes a popup dialog box appear where the user can choose a name from a list of names. Choosing a name sets a 'selectedName' variable to the selected name.
Here's my problem - the same function that I originally call to display the popup dialog needs to do some more processing with the selected name - I would like to do all of this in a single function call. Something like this:
// Called by pressing an HTML button
getSelectedName() {
display = true; // makes dialog popup appear
// Wait for user to select a name and press a 'Confirm' button
// within the dialog popup
// Once the user has selected a name, do something with it
var person: Person;
person.name = selectedName;
}
The popup dialog has Confirm/Cancel buttons - is there anyway to make the above function wait for the user to click the Confirm button, then continue on in the function?
You can create a promise that resolves once the user clicks confirm, then do any work you need only once that promise is resolved.
// Called by pressing an HTML button
getSelectedName() {
display = true; // makes dialog popup appear
// Create a promise that resolves when button is clicked.
const buttonPromise = new Promise((resolve) => {
const button = document.getElementById("my-confirm-button");
const resolver = () => {
resolve();
button.removeEventListener("click", resolver);
}
button.addEventListener("click", resolver);
});
// Once the user has selected a name, do something with it
buttonPromise.then(() => {
var person: Person;
person.name = selectedName;
})
}
I have a common serviceworker escenario, where I want catch a notification click and focus the tab where the notification has come from. However, clients variable is always empty, its lenght is 0
console.log("sw startup");
self.addEventListener('install', function (event) {
console.log("SW installed");
});
self.addEventListener('activate', function (event) {
console.log("SW activated");
});
self.addEventListener("notificationclick", function (e) {
// Android doesn't automatically close notifications on click
console.log(e);
e.notification.close();
// Focus tab if open
e.waitUntil(clients.matchAll({
type: 'window'
}).then(function (clientList) {
console.log("clients:" + clientList.length);
for (var i = 0; i < clientList.length; ++i) {
var client = clientList[i];
if (client.url === '/' && 'focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow('/');
}
}));
});
And the registration is this one:
this.doNotify = function (notification) {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js').then(function (reg) {
requestCreateNotification(notification, reg);
}, function (err) {
console.log('sw reg error:' + err);
});
}
...
}
chrome://serviceworker-internals/ output shows that registration and installation are fine. However, when a notification is pushed, clientList is empty. I have tried removing the filter type:'window' but the result is still the same. As clients are empty, a new window is always opened. What am I doing wrong?
The suspicion in your own comment is correct. A page is controlled by a service worker on navigation to an origin that the service worker is registered for. So the original page load that actually initializes the service worker is not itself controlled. That's why the worker only finds your tab once you visit with a new tab or do a refresh.
However (as Jeff Posnick points out in the comments) you can get uncontrolled pages as follows: ServiceWorkerClients.matchAll({includeUncontrolled: true, type: 'window'}).
Try making the service worker immediately claim the page.
E.g.:
self.addEventListener('install', event => event.waitUntil(self.skipWaiting()));
self.addEventListener('activate', event => event.waitUntil(self.clients.claim()));
For a more complex example, see https://serviceworke.rs/immediate-claim.html.