1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
|
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-env mozilla/frame-script */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var RefreshBlocker = {
PREF: "accessibility.blockautorefresh",
// Bug 1247100 - When a refresh is caused by an HTTP header,
// onRefreshAttempted will be fired before onLocationChange.
// When a refresh is caused by a <meta> tag in the document,
// onRefreshAttempted will be fired after onLocationChange.
//
// We only ever want to send a message to the parent after
// onLocationChange has fired, since the parent uses the
// onLocationChange update to clear transient notifications.
// Sending the message before onLocationChange will result in
// us creating the notification, and then clearing it very
// soon after.
//
// To account for both cases (onRefreshAttempted before
// onLocationChange, and onRefreshAttempted after onLocationChange),
// we'll hold a mapping of DOM Windows that we see get
// sent through both onLocationChange and onRefreshAttempted.
// When either run, they'll check the WeakMap for the existence
// of the DOM Window. If it doesn't exist, it'll add it. If
// it finds it, it'll know that it's safe to send the message
// to the parent, since we know that both have fired.
//
// The DOM Window is removed from blockedWindows when we notice
// the nsIWebProgress change state to STATE_STOP for the
// STATE_IS_WINDOW case.
//
// DOM Windows are mapped to a JS object that contains the data
// to be sent to the parent to show the notification. Since that
// data is only known when onRefreshAttempted is fired, it's only
// ever stashed in the map if onRefreshAttempted fires first -
// otherwise, null is set as the value of the mapping.
blockedWindows: new WeakMap(),
init() {
if (Services.prefs.getBoolPref(this.PREF)) {
this.enable();
}
Services.prefs.addObserver(this.PREF, this);
},
uninit() {
if (Services.prefs.getBoolPref(this.PREF)) {
this.disable();
}
Services.prefs.removeObserver(this.PREF, this);
},
observe(subject, topic, data) {
if (topic == "nsPref:changed" && data == this.PREF) {
if (Services.prefs.getBoolPref(this.PREF)) {
this.enable();
} else {
this.disable();
}
}
},
enable() {
this._filter = Cc[
"@mozilla.org/appshell/component/browser-status-filter;1"
].createInstance(Ci.nsIWebProgress);
this._filter.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_ALL);
this._filter.target = tabEventTarget;
let webProgress = docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
webProgress.addProgressListener(this._filter, Ci.nsIWebProgress.NOTIFY_ALL);
addMessageListener("RefreshBlocker:Refresh", this);
},
disable() {
let webProgress = docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
webProgress.removeProgressListener(this._filter);
this._filter.removeProgressListener(this);
this._filter = null;
removeMessageListener("RefreshBlocker:Refresh", this);
},
send(data) {
sendAsyncMessage("RefreshBlocker:Blocked", data);
},
/**
* Notices when the nsIWebProgress transitions to STATE_STOP for
* the STATE_IS_WINDOW case, which will clear any mappings from
* blockedWindows.
*/
onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
if (
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP
) {
this.blockedWindows.delete(aWebProgress.DOMWindow);
}
},
/**
* Notices when the location has changed. If, when running,
* onRefreshAttempted has already fired for this DOM Window, will
* send the appropriate refresh blocked data to the parent.
*/
onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
let win = aWebProgress.DOMWindow;
if (this.blockedWindows.has(win)) {
let data = this.blockedWindows.get(win);
if (data) {
// We saw onRefreshAttempted before onLocationChange, so
// send the message to the parent to show the notification.
this.send(data);
}
} else {
this.blockedWindows.set(win, null);
}
},
/**
* Notices when a refresh / reload was attempted. If, when running,
* onLocationChange has not yet run, will stash the appropriate data
* into the blockedWindows map to be sent when onLocationChange fires.
*/
onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
let win = aWebProgress.DOMWindow;
let outerWindowID = win.windowUtils.outerWindowID;
let data = {
URI: aURI.spec,
delay: aDelay,
sameURI: aSameURI,
outerWindowID,
};
if (this.blockedWindows.has(win)) {
// onLocationChange must have fired before, so we can tell the
// parent to show the notification.
this.send(data);
} else {
// onLocationChange hasn't fired yet, so stash the data in the
// map so that onLocationChange can send it when it fires.
this.blockedWindows.set(win, data);
}
return false;
},
receiveMessage(message) {
let data = message.data;
if (message.name == "RefreshBlocker:Refresh") {
let win = Services.wm.getOuterWindowWithId(data.outerWindowID);
let refreshURI = win.docShell.QueryInterface(Ci.nsIRefreshURI);
let URI = Services.io.newURI(data.URI);
refreshURI.forceRefreshURI(URI, null, data.delay, true);
}
},
QueryInterface: ChromeUtils.generateQI([
Ci.nsIWebProgressListener2,
Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference,
]),
};
RefreshBlocker.init();
addEventListener("unload", () => {
RefreshBlocker.uninit();
});
|