How can I globally and PERMANENTLY disable chromes ability to open
"Leave Site?" "Changes may not be saved" popups.
I want to close every tab of every chrome window and I know what I'm doing I don't need the nanny app to prevent my shutdown or get in my way when I'm trying to close out.
I tried tampermonkey scripts for disabling "onbeforeunload" events but they're not stopping this obnoxious behavior from chrome.
Install a beforeunload listener before the page does by declaring #run-at document-start in the metadata block.
Call stopImmediatePropagation to prevent the subsequently added page listeners from seeing the event.
Also clear window.onbeforeunload.
// ==UserScript==
// #name ignore beforeunload
// #match *://*/*
// #grant none
// #run-at document-start
// ==/UserScript==
window.addEventListener('beforeunload', e => {
window.onbeforeunload = null;
e.stopImmediatePropagation();
});
Related
I'd like to listen for button press events on an xbox one controller, but seems that many solutions to this are experimental and under development. Most examples I've found showing how to receive input from a gamepad use a polling method to repeatedly check the gamepad for events, but for my application it is crucial that I can record the exact timing of the button press, and I don't want to miss button presses in between scanning the game pad.
I can use either Firefox or Chrome but I haven't gotten the following solutions to work in either browser yet:
Firefox: According to this page https://www.chromestatus.com/feature/5989275208253440#, "gamepad button and axis events are implemented in Firefox behind the flag dom.gamepad.non_standard_events.enabled". I have enabled this in Firefox but listening to state change events (as described at https://www.smashingmagazine.com/2015/11/gamepad-api-in-web-games/) still has no effect.
Chrome: This github page https://github.com/MozillaReality/gamepad-plus/blob/master/README.md looks like it can extend the Gamepad API so I can also listen for button press events in Chrome, but when I try to compile the JavaScript as a standalone module using npm run build I get the error node_modules_missing and the build fails.
I'd appreciate advice on how to enable .addEventListener('gamepadbuttondown'... to work in either of these browsers.
Thanks to a codepen example by Christopher Van Wiemeersch at https://codepen.io/cvan/pen/aOzgGE I have found a solution in Firefox.
First I opened a Firefox browser (version 73.0.1) and entered about:config in the URL. Then I toggled dom.gamepad.non_standard_events.enabled to true.
Then I used the following functions/listeners from Christopher's codepen:
var gamepadConnected = function (e) {
console.log('Gamepad connected at index %d: %s. %d buttons, %d axes.',
e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length);
};
var gamepadDisconnected = function (e) {
console.log('Gamepad removed at index %d: %s.', e.gamepad.index, e.gamepad.id);
};
var gamepadButtonDown = function (e) {
console.log('Gamepad button down at index %d: %s. Button: %d.',
e.gamepad.index, e.gamepad.id, e.button);
};
var gamepadButtonUp = function (e) {
console.log('Gamepad button up at index %d: %s. Button: %d.',
e.gamepad.index, e.gamepad.id, e.button);
};
window.addEventListener('gamepadconnected', gamepadConnected);
window.addEventListener('gamepaddisconnected', gamepadDisconnected);
window.addEventListener('gamepadbuttondown', gamepadButtonDown);
window.addEventListener('gamepadbuttonup', gamepadButtonUp);
Then when a button is pressed I record the timestamp using var timestamp = new Date().getTime().
User clicks a button and in click event handler we postMessage to iframe. Iframe handles it in message event handler and calls element.requestFullscreen(). In older browsers it worked (eg. in Chrome 65) but in current (72) it errors with Failed to execute 'requestFullscreen' on 'Element': API can only be initiated by a user gesture..
Is there a way to transfer "gesture initiated" flag in postMessage call?
Note that iframe has allow="fullscreen" attribute.
As always with iframes, it depends on here it is hosted relative to the parent document.
If you are running a same-origin document in that frame, then you can simply call requestFullScreen directly on the element you wish form the main doc:
const frameDoc = frame.contentDocument;
frameDoc.getElementById('el-to-fs').requestFullscreen(); // assuming polyfill
jsfiddle Since StackSnippetsĀ® overly protected iframes won't allow access nor fullscreen.
But if you were, you wouldn't have had this issue in Chrome (you'd still have had in FF).
Because even though it is true that this method requires an user-gesture, postMessage being fast enough, you are able to use it with a same-origin frame in Chrome.
What happens in this browser is that a cross-origin document must have received an user-gesture before being able to call requestFullscreen.
Once the cross-origin frame has been marked as interacted-by-the-user, then you'll be able to call requestFullscreen even from main-doc's events: jsfiddle (still, only in Chrome).
But, for a cross-browser solution, you'd have to
load this content as same-origin (e.g using a proxy)
or to use a small hack where you set the <iframe> in fullscreen mode and let the inner document know you did so, so it can style its document accordingly: jsfiddle
main.js
onclick = e => {
frame.contentWindow.postMessage("you're going fullscreen", '*');
frame.requestFullscreen();
};
frame.js
onmessage = e => {
if(message.data === "you're going fullscreen") {
// trigger our special fullscreen CSS
document.documentElement.classList.add('fullscreen');
// do some DOM cleaning if needed
}
else if(message.data === "you're exiting fullscreen") {
document.documentElement.classList.remove('fullscreen');
}
};
Summary: I need to find a way to accomplish with programmatic injection the same exact behaviour as using content_scripts > matches with "all_frames": true on a manifest. Why? because it is the only way I've found of injecting iframe's content in an extension page without having Cross-Origin errors.
I'm moving to optional_permissions on a Chrome extension and I'm on a dead end.
What I want:
Move this behaviour to optional_permissions in order to be able to add more hosts in the future. With the current code, by adding one new host on content_scripts > matches the extension is disabled by Chrome.
For the move, I removed content_scripts in the manifest and I added "optional_permissions": ["*://*/"],. Then, I successfully implemented a dialog asking new permissions to the user with chrome.permissions.request.
As I said before, the problem is how to inject the iframe's content in an extension page.
What I've tried:
chrome.declarativeContent.RequestContentScript (mentioned here) with allFrames: true. I can only see the script running if I enter the URL directly, nothing happens when that URL is set in an iframe.
chrome.tabs.onUpdated: url is undefined for an extension page. Also, the iframe url is not detected.
Call chrome.tabs.executeScript with allFrames: true as soon as I load the first iframe. By doing this I get an exception Cannot access contents of the page. Extension manifest must request permission to access the respective host. and the "respective host" is chrome-extension://, which is not a valid host if you want to add it to the permissions.
I'm lost. I couldn't find a way to simulate the same behaviour as content_scripts > matches with programmatic injection.
Note: using webNavigation API is not an option since the extension is live and it has thousands of users. Because of this, I can not use the frameId property for executeScript. Thus, my only option with executeScript was to inject all frames but the chrome-extension host issue do not let me continue.
Update: I was able to accomplish what I wanted but only on an HTTP host. I used chrome.tabs.executeScript (option 3).
The question remains on how to make this work on an extension page.
You cannot run content scripts in any extension page, including your own.
If you want to run code in a subframe of your extension page, then you have to use frameId. There are two ways to do this, with and without webNavigation.
I've put all code snippets in this answer together (with some buttons to invoke the individual code snippets) and shared it at https://robwu.nl/s/optional_permissions-script-subframe.zip
To try it out, download and extract the zip file, load the extension at chrome://extensions and click on the extension button to open the test page.
Request optional permissions
Since the goal is to programmatically run scripts with optional permissions, you need to request the permission. My example will use example.com.
If you want to use the webNavigation API too, include its permission in the permission request too.
chrome.permissions.request({
// permissions: ['webNavigation'], // uncomment if you want this.
origins: ['*://*.example.com/*'],
}, function(granted) {
alert('Permission was ' + (granted ? '' : 'not ') + 'granted!');
});
Inject script in subframe
Once you have a tab ID and frameId, injecting scripts in a specific frame is easy. Because of the tabId requirement, this method can only work for frames in tabs, not for frames in your browserAction/pageAction popup or background page!
To demonstrate that code execution succeeds, my examples below will call the next injectInFrame function once the tabId and frameId is known.
function injectInFrame(tabId, frameId) {
chrome.tabs.executeScript(tabId, {
frameId,
code: 'document.body.textContent = "The document content replaced with content at " + new Date().toLocaleString();',
});
}
If you want to run code not just in the specific frame, but all sub frames of that frame, just add allFrames: true to the chrome.tabs.executeScript call.
Option 1: Use webNavigation to find frameId
Use chrome.tabs.getCurrent to find the ID of the tab where the script runs (or chrome.tabs.query with {active:true,currentWindow:true} if you want to know the current tabId from another script (e.g. background script).
After that, use chrome.webNavigation.getAllFrames to query all frames in the tab. The primary way of identifying a frame is by the URL of the page, so you have a problem if the framed page redirects elsewhere, or if there are multiple frames with the same URL. Here is an example:
// Assuming that you already have a frame in your document,
// i.e. <iframe src="https://example.com"></iframe>
chrome.tabs.getCurrent(function(tab) {
chrome.webNavigation.getAllFrames({
tabId: tab.id,
}, function(frames) {
for (var frame of frames) {
if (frame.url === 'https://example.com/') {
injectInFrame(tab.id, frame.frameId);
break;
}
}
});
});
Option 2: Use helper page in the frame to find frameId
The option with webNavigation looks simple but has two main disadvantages:
It requires the webNavigation permission (causing the "Read your browsing history" permission warning)
The identification of the frame can fail if there are multiple frames with the same URL.
An alternative is to first open an extension page that sends an extension message, and find the frameId (and tab ID) in the metadata that is made available in the second parameter of the chrome.runtime.onMessage listener. This code is more complicated than the other option, but it is more reliable and does not require any additional permissions.
framehelper.html
<script src="framehelper.js"></script>
framehelper.js
var parentOrigin = location.ancestorOrigins[location.ancestorOrigins.length - 1];
if (parentOrigin === location.origin) {
// Only send a message if the frame was opened by ourselves.
chrome.runtime.sendMessage(location.hash.slice(1));
}
Code to be run in your extension page:
chrome.runtime.onMessage.addListener(frameMessageListener);
var randomMessage = 'Random message: ' + Math.random();
var f = document.createElement('iframe');
f.src = chrome.runtime.getURL('framehelper.html') + '#' + randomMessage;
document.body.appendChild(f);
function frameMessageListener(msg, sender) {
if (msg !== randomMessage) return;
var tabId = sender.tab.id;
var frameId = sender.frameId;
chrome.runtime.onMessage.removeListener(frameMessageListener);
// Note: This will cause the script to be run on the first load.
// If the frame redirects elsewhere, then the injection can seemingly fail.
f.addEventListener('load', function onload() {
f.removeEventListener('load', onload);
injectInFrame(tabId, frameId);
});
f.src = 'https://example.com';
}
Why
window.addEventListener('popstate', () => alert('pop'));
window.history.pushState(null, '', '/foo');
does not alert pop ?
NB: Testing on latest chrome
--
According to MDN:
A popstate event is dispatched to the window every time the active history entry changes. If the history entry being activated was created by a call to pushState or affected by a call to replaceState, the popstate event's state property contains a copy of the history entry's state object.
So why my pushState does not trigger the popstate event?
You can manually trigger popstate event on window every time you call history.pushState().
history.pushState(state, '', url);
var popStateEvent = new PopStateEvent('popstate', { state: state });
dispatchEvent(popStateEvent);
The paragraph you reference is a little ambiguous. Reading the example on the same page, it is clear that popstate is only triggered when the user clicks the back button, not when the script calls pushState().
You are reading MDN's "guide page" which is not meant to be normative.
Consider reading MDN's "documentation page" for WindowEventHandlers.onpopstate instead:
Note that just calling history.pushState() or history.replaceState() won't trigger a popstate event. The popstate event is only triggered by doing a browser action such as a clicking on the back button (or calling history.back() in JavaScript). And the event is only triggered when the user navigates between two history entries for the same document.
Another undocumented way of triggering popstate is by direct manipulation of the window.location object.
// this also triggers popstate
window.location.hash = "#some-new-hash"
My solution:
var url = "http://awesome.website.net/route/to/paradise";
window.history.pushState({}, "", url);
window.history.pushState({}, "", url); // yes twice
window.history.back();
It will trigger a soft-navigation via 'popstate' event and no HTTP request.
Document https://developer.mozilla.org/en-US/docs/Web/Events/popstate says:
Note that just calling history.pushState() or history.replaceState() won't trigger a popstate event. The popstate event will been triggered by doing a browser action such as a click on the back or forward button (or calling history.back() or history.forward() in JavaScript).
I ran into this too... I think the event only fires if you have something at the top level of your data object that is distinct from the previous state.
Try this:
var data = {rand: Math.random()};
window.history.pushState(data, '', '/foo');
I am working on Chrome Extensions. I want to know that is it Possible to invoke keyboard Function keys using Chrome Extensions.
Thanks,
NVN.
EDIT: Do you want to simulate function key presses on a page or listen for physical key presses from the user? If you want want to procedurally trigger function key actions, you can't. As Rob pointed out, scripted events only activate scripted event listeners, and do not trigger default behavior.
In order to detect function key presses, you need to bind your events to keyup -- not to keypress, which doesn't fire for several non-printable keystrokes, like function keys and arrow keys.
document.documentElement.addEventListener("keyup", function(e) {
if(e.keyCode == 113) {
// if F2 is pressed...
// F1 is keycode 112, and it increments from there
}
});
Some function keys do things already (F1 opens help, F5 refreshes, etc.). If you have a good reason for preventing this behavior (e.g. you're making an immersive full-screen app like a VNC viewer that shouldn't exhibit normal browser behavior), you can use preventDefault on keydown to stop that potentially disruptive action:
document.documentElement.addEventListener("keydown", function(e) {
if(e.keyCode == 112) {
// if F1 is pressed, don't open help
e.preventDefault();
}
});
To see how to handle key presses for your Chrome extension, see my answer on Activate extension via short cut key; that should be exactly what you need for the rest. Briefly:
the code I gave above goes in a content script that gets added to each page
the event handlers functions perform some action, either within the content script on the page or by passing a message to the background page