1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 * This file has two actors, RefreshBlockerChild js a window actor which
7 * handles the refresh notifications. RefreshBlockerObserverChild is a process
8 * actor that enables refresh blocking on each docshell that is created.
11 var EXPORTED_SYMBOLS = ["RefreshBlockerChild", "RefreshBlockerObserverChild"];
13 const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
15 const REFRESHBLOCKING_PREF = "accessibility.blockautorefresh";
17 var progressListener = {
18 // Bug 1247100 - When a refresh is caused by an HTTP header,
19 // onRefreshAttempted will be fired before onLocationChange.
20 // When a refresh is caused by a <meta> tag in the document,
21 // onRefreshAttempted will be fired after onLocationChange.
23 // We only ever want to send a message to the parent after
24 // onLocationChange has fired, since the parent uses the
25 // onLocationChange update to clear transient notifications.
26 // Sending the message before onLocationChange will result in
27 // us creating the notification, and then clearing it very
30 // To account for both cases (onRefreshAttempted before
31 // onLocationChange, and onRefreshAttempted after onLocationChange),
32 // we'll hold a mapping of DOM Windows that we see get
33 // sent through both onLocationChange and onRefreshAttempted.
34 // When either run, they'll check the WeakMap for the existence
35 // of the DOM Window. If it doesn't exist, it'll add it. If
36 // it finds it, it'll know that it's safe to send the message
37 // to the parent, since we know that both have fired.
39 // The DOM Window is removed from blockedWindows when we notice
40 // the nsIWebProgress change state to STATE_STOP for the
41 // STATE_IS_WINDOW case.
43 // DOM Windows are mapped to a JS object that contains the data
44 // to be sent to the parent to show the notification. Since that
45 // data is only known when onRefreshAttempted is fired, it's only
46 // ever stashed in the map if onRefreshAttempted fires first -
47 // otherwise, null is set as the value of the mapping.
48 blockedWindows: new WeakMap(),
51 * Notices when the nsIWebProgress transitions to STATE_STOP for
52 * the STATE_IS_WINDOW case, which will clear any mappings from
55 onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
57 aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
58 aStateFlags & Ci.nsIWebProgressListener.STATE_STOP
60 this.blockedWindows.delete(aWebProgress.DOMWindow);
65 * Notices when the location has changed. If, when running,
66 * onRefreshAttempted has already fired for this DOM Window, will
67 * send the appropriate refresh blocked data to the parent.
69 onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
70 let win = aWebProgress.DOMWindow;
71 if (this.blockedWindows.has(win)) {
72 let data = this.blockedWindows.get(win);
74 // We saw onRefreshAttempted before onLocationChange, so
75 // send the message to the parent to show the notification.
79 this.blockedWindows.set(win, null);
84 * Notices when a refresh / reload was attempted. If, when running,
85 * onLocationChange has not yet run, will stash the appropriate data
86 * into the blockedWindows map to be sent when onLocationChange fires.
88 onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
89 let win = aWebProgress.DOMWindow;
92 browsingContext: win.browsingContext,
98 if (this.blockedWindows.has(win)) {
99 // onLocationChange must have fired before, so we can tell the
100 // parent to show the notification.
101 this.send(win, data);
103 // onLocationChange hasn't fired yet, so stash the data in the
104 // map so that onLocationChange can send it when it fires.
105 this.blockedWindows.set(win, data);
112 // Due to the |nsDocLoader| calling its |nsIWebProgressListener|s in
113 // reverse order, this will occur *before* the |BrowserChild| can send its
114 // |OnLocationChange| event to the parent, but we need this message to
115 // arrive after to ensure that the refresh blocker notification is not
116 // immediately cleared by the |OnLocationChange| from |BrowserChild|.
118 // An exception can occur if refresh blocking was turned off
119 // during a pageload.
121 let actor = win.windowGlobalChild.getActor("RefreshBlocker");
123 actor.sendAsyncMessage("RefreshBlocker:Blocked", data);
129 QueryInterface: ChromeUtils.generateQI([
130 "nsIWebProgressListener2",
131 "nsIWebProgressListener",
132 "nsISupportsWeakReference",
136 class RefreshBlockerChild extends JSWindowActorChild {
138 // If the refresh blocking preference is turned off, all of the
139 // RefreshBlockerChild actors will get destroyed, so disable
140 // refresh blocking only in this case.
141 if (!Services.prefs.getBoolPref(REFRESHBLOCKING_PREF)) {
142 this.disable(this.docShell);
147 ChromeUtils.domProcessChild
148 .getActor("RefreshBlockerObserver")
149 .enable(this.docShell);
153 ChromeUtils.domProcessChild
154 .getActor("RefreshBlockerObserver")
155 .disable(this.docShell);
158 receiveMessage(message) {
159 let data = message.data;
161 switch (message.name) {
162 case "RefreshBlocker:Refresh":
163 let docShell = data.browsingContext.docShell;
164 let refreshURI = docShell.QueryInterface(Ci.nsIRefreshURI);
165 let URI = Services.io.newURI(data.URI);
166 refreshURI.forceRefreshURI(URI, null, data.delay);
169 case "PreferenceChanged":
170 if (data.isEnabled) {
171 this.enable(this.docShell);
173 this.disable(this.docShell);
179 class RefreshBlockerObserverChild extends JSProcessActorChild {
182 this.filtersMap = new Map();
185 observe(subject, topic, data) {
187 case "webnavigation-create":
188 case "chrome-webnavigation-create":
189 if (Services.prefs.getBoolPref(REFRESHBLOCKING_PREF)) {
190 this.enable(subject.QueryInterface(Ci.nsIDocShell));
194 case "webnavigation-destroy":
195 case "chrome-webnavigation-destroy":
196 if (Services.prefs.getBoolPref(REFRESHBLOCKING_PREF)) {
197 this.disable(subject.QueryInterface(Ci.nsIDocShell));
204 if (this.filtersMap.has(docShell)) {
209 "@mozilla.org/appshell/component/browser-status-filter;1"
210 ].createInstance(Ci.nsIWebProgress);
212 filter.addProgressListener(progressListener, Ci.nsIWebProgress.NOTIFY_ALL);
214 this.filtersMap.set(docShell, filter);
216 let webProgress = docShell
217 .QueryInterface(Ci.nsIInterfaceRequestor)
218 .getInterface(Ci.nsIWebProgress);
219 webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
223 let filter = this.filtersMap.get(docShell);
228 let webProgress = docShell
229 .QueryInterface(Ci.nsIInterfaceRequestor)
230 .getInterface(Ci.nsIWebProgress);
231 webProgress.removeProgressListener(filter);
233 filter.removeProgressListener(progressListener);
234 this.filtersMap.delete(docShell);