Line data Source code
1 : /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : /* eslint-env mozilla/frame-script */
7 : /* eslint no-unused-vars: ["error", {args: "none"}] */
8 : /* global sendAsyncMessage */
9 :
10 0 : ChromeUtils.import("resource://gre/modules/Services.jsm");
11 1 : ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
12 :
13 0 : ChromeUtils.defineModuleGetter(this, "AutoScrollController",
14 0 : "resource://gre/modules/AutoScrollController.jsm");
15 0 : ChromeUtils.defineModuleGetter(this, "BrowserUtils",
16 0 : "resource://gre/modules/BrowserUtils.jsm");
17 0 : ChromeUtils.defineModuleGetter(this, "SelectContentHelper",
18 0 : "resource://gre/modules/SelectContentHelper.jsm");
19 0 : ChromeUtils.defineModuleGetter(this, "FindContent",
20 0 : "resource://gre/modules/FindContent.jsm");
21 0 : ChromeUtils.defineModuleGetter(this, "PrintingContent",
22 1 : "resource://gre/modules/PrintingContent.jsm");
23 0 : ChromeUtils.defineModuleGetter(this, "RemoteFinder",
24 1 : "resource://gre/modules/RemoteFinder.jsm");
25 :
26 2 : XPCOMUtils.defineLazyProxy(this, "SelectionSourceContent",
27 0 : "resource://gre/modules/SelectionSourceContent.jsm");
28 :
29 0 : XPCOMUtils.defineLazyProxy(this, "DateTimePickerContent", () => {
30 0 : let tmp = {};
31 0 : ChromeUtils.import("resource://gre/modules/DateTimePickerContent.jsm", tmp);
32 0 : return new tmp.DateTimePickerContent(this);
33 0 : });
34 :
35 0 : var global = this;
36 :
37 :
38 : // Lazily load the finder code
39 0 : addMessageListener("Finder:Initialize", function() {
40 0 : let {RemoteFinderListener} = ChromeUtils.import("resource://gre/modules/RemoteFinder.jsm", {});
41 0 : new RemoteFinderListener(global);
42 : });
43 :
44 1 : var AutoScrollListener = {
45 0 : handleEvent(event) {
46 0 : if (event.isTrusted &
47 0 : !event.defaultPrevented &&
48 0 : event.button == 1) {
49 0 : if (!this._controller) {
50 0 : this._controller = new AutoScrollController(global);
51 : }
52 0 : this._controller.handleEvent(event);
53 : }
54 : }
55 : };
56 0 : Services.els.addSystemEventListener(global, "mousedown", AutoScrollListener, true);
57 :
58 1 : addEventListener("MozOpenDateTimePicker", DateTimePickerContent);
59 :
60 0 : var PopupBlocking = {
61 0 : popupData: null,
62 0 : popupDataInternal: null,
63 :
64 1 : init() {
65 0 : addEventListener("DOMPopupBlocked", this, true);
66 0 : addEventListener("pageshow", this, true);
67 0 : addEventListener("pagehide", this, true);
68 :
69 1 : addMessageListener("PopupBlocking:UnblockPopup", this);
70 0 : addMessageListener("PopupBlocking:GetBlockedPopupList", this);
71 : },
72 :
73 0 : receiveMessage(msg) {
74 0 : switch (msg.name) {
75 0 : case "PopupBlocking:UnblockPopup": {
76 0 : let i = msg.data.index;
77 0 : if (this.popupData && this.popupData[i]) {
78 0 : let data = this.popupData[i];
79 0 : let internals = this.popupDataInternal[i];
80 0 : let dwi = internals.requestingWindow;
81 :
82 : // If we have a requesting window and the requesting document is
83 : // still the current document, open the popup.
84 0 : if (dwi && dwi.document == internals.requestingDocument) {
85 0 : dwi.open(data.popupWindowURIspec, data.popupWindowName, data.popupWindowFeatures);
86 : }
87 : }
88 0 : break;
89 : }
90 :
91 0 : case "PopupBlocking:GetBlockedPopupList": {
92 0 : let popupData = [];
93 0 : let length = this.popupData ? this.popupData.length : 0;
94 :
95 : // Limit 15 popup URLs to be reported through the UI
96 0 : length = Math.min(length, 15);
97 :
98 0 : for (let i = 0; i < length; i++) {
99 0 : let popupWindowURIspec = this.popupData[i].popupWindowURIspec;
100 :
101 0 : if (popupWindowURIspec == global.content.location.href) {
102 0 : popupWindowURIspec = "<self>";
103 : } else {
104 : // Limit 500 chars to be sent because the URI will be cropped
105 : // by the UI anyway, and data: URIs can be significantly larger.
106 0 : popupWindowURIspec = popupWindowURIspec.substring(0, 500);
107 : }
108 :
109 0 : popupData.push({popupWindowURIspec});
110 : }
111 :
112 0 : sendAsyncMessage("PopupBlocking:ReplyGetBlockedPopupList", {popupData});
113 0 : break;
114 : }
115 : }
116 : },
117 :
118 1 : handleEvent(ev) {
119 1 : switch (ev.type) {
120 : case "DOMPopupBlocked":
121 0 : return this.onPopupBlocked(ev);
122 : case "pageshow":
123 0 : return this._removeIrrelevantPopupData();
124 : case "pagehide":
125 1 : return this._removeIrrelevantPopupData(ev.target);
126 : }
127 0 : return undefined;
128 : },
129 :
130 1 : onPopupBlocked(ev) {
131 0 : if (!this.popupData) {
132 0 : this.popupData = [];
133 0 : this.popupDataInternal = [];
134 : }
135 :
136 0 : let obj = {
137 0 : popupWindowURIspec: ev.popupWindowURI ? ev.popupWindowURI.spec : "about:blank",
138 0 : popupWindowFeatures: ev.popupWindowFeatures,
139 0 : popupWindowName: ev.popupWindowName
140 : };
141 :
142 0 : let internals = {
143 0 : requestingWindow: ev.requestingWindow,
144 0 : requestingDocument: ev.requestingWindow.document,
145 : };
146 :
147 0 : this.popupData.push(obj);
148 0 : this.popupDataInternal.push(internals);
149 0 : this.updateBlockedPopups(true);
150 : },
151 :
152 1 : _removeIrrelevantPopupData(removedDoc = null) {
153 1 : if (this.popupData) {
154 0 : let i = 0;
155 0 : let oldLength = this.popupData.length;
156 0 : while (i < this.popupData.length) {
157 0 : let {requestingWindow, requestingDocument} = this.popupDataInternal[i];
158 : // Filter out irrelevant reports.
159 0 : if (requestingWindow && requestingWindow.document == requestingDocument &&
160 0 : requestingDocument != removedDoc) {
161 0 : i++;
162 : } else {
163 0 : this.popupData.splice(i, 1);
164 0 : this.popupDataInternal.splice(i, 1);
165 : }
166 : }
167 0 : if (this.popupData.length == 0) {
168 0 : this.popupData = null;
169 0 : this.popupDataInternal = null;
170 : }
171 0 : if (!this.popupData || oldLength > this.popupData.length) {
172 0 : this.updateBlockedPopups(false);
173 : }
174 : }
175 : },
176 :
177 1 : updateBlockedPopups(freshPopup) {
178 0 : sendAsyncMessage("PopupBlocking:UpdateBlockedPopups",
179 0 : {
180 0 : count: this.popupData ? this.popupData.length : 0,
181 0 : freshPopup
182 : });
183 : },
184 : };
185 1 : PopupBlocking.init();
186 :
187 1 : var Printing = {
188 0 : MESSAGES: [
189 : "Printing:Preview:Enter",
190 : "Printing:Preview:Exit",
191 : "Printing:Preview:Navigate",
192 : "Printing:Preview:ParseDocument",
193 : "Printing:Print",
194 : ],
195 :
196 0 : init() {
197 6 : this.MESSAGES.forEach(msgName => addMessageListener(msgName, this));
198 0 : addEventListener("PrintingError", this, true);
199 0 : addEventListener("printPreviewUpdate", this, true);
200 : },
201 :
202 1 : handleEvent(event) {
203 0 : return PrintingContent.handleEvent(global, event);
204 : },
205 :
206 0 : receiveMessage(message) {
207 0 : return PrintingContent.receiveMessage(global, message);
208 : },
209 : };
210 0 : Printing.init();
211 :
212 : function SwitchDocumentDirection(aWindow) {
213 : // document.dir can also be "auto", in which case it won't change
214 0 : if (aWindow.document.dir == "ltr" || aWindow.document.dir == "") {
215 0 : aWindow.document.dir = "rtl";
216 0 : } else if (aWindow.document.dir == "rtl") {
217 0 : aWindow.document.dir = "ltr";
218 : }
219 0 : for (let run = 0; run < aWindow.frames.length; run++) {
220 0 : SwitchDocumentDirection(aWindow.frames[run]);
221 : }
222 : }
223 :
224 0 : addMessageListener("SwitchDocumentDirection", () => {
225 0 : SwitchDocumentDirection(content.window);
226 : });
227 :
228 0 : var FindBar = {
229 : /* Please keep in sync with toolkit/content/widgets/findbar.xml */
230 1 : FIND_NORMAL: 0,
231 1 : FIND_TYPEAHEAD: 1,
232 1 : FIND_LINKS: 2,
233 :
234 1 : _findMode: 0,
235 :
236 : /**
237 : * _findKey and _findModifiers are used to determine whether a keypress
238 : * is a user attempting to use the find shortcut, after which we'll
239 : * route keypresses to the parent until we know the findbar has focus
240 : * there. To do this, we need shortcut data from the parent.
241 : */
242 1 : _findKey: null,
243 1 : _findModifiers: null,
244 :
245 0 : init() {
246 0 : addMessageListener("Findbar:UpdateState", this);
247 0 : Services.els.addSystemEventListener(global, "keypress", this, false);
248 0 : Services.els.addSystemEventListener(global, "mouseup", this, false);
249 1 : this._initShortcutData();
250 : },
251 :
252 0 : receiveMessage(msg) {
253 0 : switch (msg.name) {
254 : case "Findbar:UpdateState":
255 0 : this._findMode = msg.data.findMode;
256 0 : this._quickFindTimeout = msg.data.hasQuickFindTimeout;
257 0 : if (msg.data.isOpenAndFocused) {
258 0 : this._keepPassingUntilToldOtherwise = false;
259 : }
260 : break;
261 : case "Findbar:ShortcutData":
262 : // Set us up to never need this again for the lifetime of this process,
263 : // and remove the listener.
264 1 : Services.cpmm.initialProcessData.findBarShortcutData = msg.data;
265 1 : Services.cpmm.removeMessageListener("Findbar:ShortcutData", this);
266 1 : this._initShortcutData(msg.data);
267 : break;
268 : }
269 : },
270 :
271 1 : handleEvent(event) {
272 0 : switch (event.type) {
273 : case "keypress":
274 0 : this._onKeypress(event);
275 : break;
276 : case "mouseup":
277 0 : this._onMouseup(event);
278 : break;
279 : }
280 : },
281 :
282 : /**
283 : * Use initial process data for find key/modifier data if we have it.
284 : * Otherwise, add a listener so we get the data when the parent process has
285 : * it.
286 : */
287 1 : _initShortcutData(data = Services.cpmm.initialProcessData.findBarShortcutData) {
288 2 : if (data) {
289 1 : this._findKey = data.key;
290 1 : this._findModifiers = data.modifiers;
291 : } else {
292 1 : Services.cpmm.addMessageListener("Findbar:ShortcutData", this);
293 : }
294 : },
295 :
296 : /**
297 : * Check whether this key event will start the findbar in the parent,
298 : * in which case we should pass any further key events to the parent to avoid
299 : * them being lost.
300 : * @param aEvent the key event to check.
301 : */
302 1 : _eventMatchesFindShortcut(aEvent) {
303 0 : let modifiers = this._findModifiers;
304 0 : if (!modifiers) {
305 0 : return false;
306 : }
307 0 : return aEvent.ctrlKey == modifiers.ctrlKey && aEvent.altKey == modifiers.altKey &&
308 0 : aEvent.shiftKey == modifiers.shiftKey && aEvent.metaKey == modifiers.metaKey &&
309 0 : aEvent.key == this._findKey;
310 0 : },
311 :
312 : /**
313 : * Returns whether FAYT can be used for the given event in
314 : * the current content state.
315 : */
316 0 : _canAndShouldFastFind() {
317 0 : let should = false;
318 0 : let can = BrowserUtils.canFastFind(content);
319 0 : if (can) {
320 : // XXXgijs: why all these shenanigans? Why not use the event's target?
321 0 : let focusedWindow = {};
322 0 : let elt = Services.focus.getFocusedElementForWindow(content, true, focusedWindow);
323 0 : let win = focusedWindow.value;
324 0 : should = BrowserUtils.shouldFastFind(elt, win);
325 : }
326 0 : return { can, should };
327 0 : },
328 :
329 1 : _onKeypress(event) {
330 0 : const FAYT_LINKS_KEY = "'";
331 0 : const FAYT_TEXT_KEY = "/";
332 0 : if (this._eventMatchesFindShortcut(event)) {
333 0 : this._keepPassingUntilToldOtherwise = true;
334 : }
335 : // Useless keys:
336 0 : if (event.ctrlKey || event.altKey || event.metaKey || event.defaultPrevented) {
337 0 : return;
338 : }
339 :
340 : // Check the focused element etc.
341 0 : let fastFind = this._canAndShouldFastFind();
342 :
343 : // Can we even use find in this page at all?
344 0 : if (!fastFind.can) {
345 0 : return;
346 : }
347 0 : if (this._keepPassingUntilToldOtherwise) {
348 0 : this._passKeyToParent(event);
349 0 : return;
350 : }
351 0 : if (!fastFind.should) {
352 0 : return;
353 : }
354 :
355 0 : let charCode = event.charCode;
356 : // If the find bar is open and quick find is on, send the key to the parent.
357 0 : if (this._findMode != this.FIND_NORMAL && this._quickFindTimeout) {
358 0 : if (!charCode)
359 0 : return;
360 0 : this._passKeyToParent(event);
361 0 : } else {
362 0 : let key = charCode ? String.fromCharCode(charCode) : null;
363 0 : let manualstartFAYT = (key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY);
364 0 : let autostartFAYT = !manualstartFAYT && RemoteFinder._findAsYouType && key && key != " ";
365 0 : if (manualstartFAYT || autostartFAYT) {
366 0 : let mode = (key == FAYT_LINKS_KEY || (autostartFAYT && RemoteFinder._typeAheadLinksOnly)) ?
367 0 : this.FIND_LINKS : this.FIND_TYPEAHEAD;
368 : // Set _findMode immediately (without waiting for child->parent->child roundtrip)
369 : // to ensure we pass any further keypresses, too.
370 0 : this._findMode = mode;
371 0 : this._passKeyToParent(event);
372 : }
373 : }
374 0 : },
375 :
376 0 : _passKeyToParent(event) {
377 0 : event.preventDefault();
378 : // These are the properties required to dispatch another 'real' event
379 : // to the findbar in the parent in _dispatchKeypressEvent in findbar.xml .
380 : // If you make changes here, verify that that method can still do its job.
381 0 : const kRequiredProps = [
382 : "type", "bubbles", "cancelable", "ctrlKey", "altKey", "shiftKey",
383 : "metaKey", "keyCode", "charCode",
384 : ];
385 0 : let fakeEvent = {};
386 0 : for (let prop of kRequiredProps) {
387 0 : fakeEvent[prop] = event[prop];
388 : }
389 0 : sendAsyncMessage("Findbar:Keypress", fakeEvent);
390 : },
391 :
392 0 : _onMouseup(event) {
393 0 : if (this._findMode != this.FIND_NORMAL)
394 0 : sendAsyncMessage("Findbar:Mouseup");
395 : },
396 : };
397 1 : FindBar.init();
398 :
399 0 : let WebChannelMessageToChromeListener = {
400 : // Preference containing the list (space separated) of origins that are
401 : // allowed to send non-string values through a WebChannel, mainly for
402 : // backwards compatability. See bug 1238128 for more information.
403 1 : URL_WHITELIST_PREF: "webchannel.allowObject.urlWhitelist",
404 :
405 : // Cached list of whitelisted principals, we avoid constructing this if the
406 : // value in `_lastWhitelistValue` hasn't changed since we constructed it last.
407 1 : _cachedWhitelist: [],
408 1 : _lastWhitelistValue: "",
409 :
410 1 : init() {
411 2 : addEventListener("WebChannelMessageToChrome", e => {
412 0 : this._onMessageToChrome(e);
413 0 : }, true, true);
414 : },
415 :
416 1 : _getWhitelistedPrincipals() {
417 0 : let whitelist = Services.prefs.getCharPref(this.URL_WHITELIST_PREF);
418 0 : if (whitelist != this._lastWhitelistValue) {
419 0 : let urls = whitelist.split(/\s+/);
420 0 : this._cachedWhitelist = urls.map(origin =>
421 0 : Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin));
422 : }
423 0 : return this._cachedWhitelist;
424 0 : },
425 :
426 1 : _onMessageToChrome(e) {
427 : // If target is window then we want the document principal, otherwise fallback to target itself.
428 0 : let principal = e.target.nodePrincipal ? e.target.nodePrincipal : e.target.document.nodePrincipal;
429 :
430 0 : if (e.detail) {
431 0 : if (typeof e.detail != "string") {
432 : // Check if the principal is one of the ones that's allowed to send
433 : // non-string values for e.detail. They're whitelisted by site origin,
434 : // so we compare on originNoSuffix in order to avoid other origin attributes
435 : // that are not relevant here, such as containers or private browsing.
436 0 : let objectsAllowed = this._getWhitelistedPrincipals().some(whitelisted =>
437 0 : principal.originNoSuffix == whitelisted.originNoSuffix);
438 0 : if (!objectsAllowed) {
439 0 : Cu.reportError("WebChannelMessageToChrome sent with an object from a non-whitelisted principal");
440 0 : return;
441 : }
442 : }
443 0 : sendAsyncMessage("WebChannelMessageToChrome", e.detail, { eventTarget: e.target }, principal);
444 : } else {
445 0 : Cu.reportError("WebChannel message failed. No message detail.");
446 : }
447 0 : }
448 : };
449 :
450 1 : WebChannelMessageToChromeListener.init();
451 :
452 : // This should be kept in sync with /browser/base/content.js.
453 : // Add message listener for "WebChannelMessageToContent" messages from chrome scripts.
454 0 : addMessageListener("WebChannelMessageToContent", function(e) {
455 0 : if (e.data) {
456 : // e.objects.eventTarget will be defined if sending a response to
457 : // a WebChannelMessageToChrome event. An unsolicited send
458 : // may not have an eventTarget defined, in this case send to the
459 : // main content window.
460 0 : let eventTarget = e.objects.eventTarget || content;
461 :
462 : // Use nodePrincipal if available, otherwise fallback to document principal.
463 0 : let targetPrincipal = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget.document.nodePrincipal : eventTarget.nodePrincipal;
464 :
465 0 : if (e.principal.subsumes(targetPrincipal)) {
466 : // If eventTarget is a window, use it as the targetWindow, otherwise
467 : // find the window that owns the eventTarget.
468 0 : let targetWindow = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget : eventTarget.ownerGlobal;
469 :
470 0 : eventTarget.dispatchEvent(new targetWindow.CustomEvent("WebChannelMessageToContent", {
471 0 : detail: Cu.cloneInto({
472 0 : id: e.data.id,
473 0 : message: e.data.message,
474 0 : }, targetWindow),
475 : }));
476 : } else {
477 0 : Cu.reportError("WebChannel message failed. Principal mismatch.");
478 : }
479 : } else {
480 0 : Cu.reportError("WebChannel message failed. No message data.");
481 : }
482 : });
483 :
484 1 : var AudioPlaybackListener = {
485 0 : QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
486 :
487 0 : init() {
488 1 : Services.obs.addObserver(this, "audio-playback");
489 :
490 0 : addMessageListener("AudioPlayback", this);
491 0 : addEventListener("unload", () => {
492 1 : AudioPlaybackListener.uninit();
493 : });
494 : },
495 :
496 1 : uninit() {
497 1 : Services.obs.removeObserver(this, "audio-playback");
498 :
499 1 : removeMessageListener("AudioPlayback", this);
500 : },
501 :
502 1 : handleMediaControlMessage(msg) {
503 0 : let utils = global.content.QueryInterface(Ci.nsIInterfaceRequestor)
504 0 : .getInterface(Ci.nsIDOMWindowUtils);
505 0 : let suspendTypes = Ci.nsISuspendedTypes;
506 0 : switch (msg) {
507 : case "mute":
508 0 : utils.audioMuted = true;
509 : break;
510 : case "unmute":
511 0 : utils.audioMuted = false;
512 : break;
513 : case "lostAudioFocus":
514 0 : utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE_DISPOSABLE;
515 : break;
516 : case "lostAudioFocusTransiently":
517 0 : utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE;
518 : break;
519 : case "gainAudioFocus":
520 0 : utils.mediaSuspend = suspendTypes.NONE_SUSPENDED;
521 : break;
522 : case "mediaControlPaused":
523 0 : utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE_DISPOSABLE;
524 : break;
525 : case "mediaControlStopped":
526 0 : utils.mediaSuspend = suspendTypes.SUSPENDED_STOP_DISPOSABLE;
527 : break;
528 : case "resumeMedia":
529 0 : utils.mediaSuspend = suspendTypes.NONE_SUSPENDED;
530 : break;
531 : default:
532 0 : dump("Error : wrong media control msg!\n");
533 : break;
534 : }
535 : },
536 :
537 1 : observe(subject, topic, data) {
538 0 : if (topic === "audio-playback") {
539 0 : if (subject && subject.top == global.content) {
540 0 : let name = "AudioPlayback:";
541 0 : if (data === "activeMediaBlockStart") {
542 0 : name += "ActiveMediaBlockStart";
543 0 : } else if (data === "activeMediaBlockStop") {
544 0 : name += "ActiveMediaBlockStop";
545 : } else {
546 0 : name += (data === "active") ? "Start" : "Stop";
547 : }
548 0 : sendAsyncMessage(name);
549 : }
550 : }
551 : },
552 :
553 1 : receiveMessage(msg) {
554 0 : if (msg.name == "AudioPlayback") {
555 0 : this.handleMediaControlMessage(msg.data.type);
556 : }
557 : },
558 : };
559 1 : AudioPlaybackListener.init();
560 :
561 1 : var UnselectedTabHoverObserver = {
562 0 : init() {
563 0 : addMessageListener("Browser:UnselectedTabHover", this);
564 1 : addEventListener("UnselectedTabHover:Enable", this);
565 1 : addEventListener("UnselectedTabHover:Disable", this);
566 : },
567 1 : receiveMessage(message) {
568 0 : Services.obs.notifyObservers(content.window, "unselected-tab-hover",
569 0 : message.data.hovered);
570 : },
571 1 : handleEvent(event) {
572 0 : sendAsyncMessage("UnselectedTabHover:Toggle",
573 0 : { enable: event.type == "UnselectedTabHover:Enable" });
574 : }
575 : };
576 1 : UnselectedTabHoverObserver.init();
577 :
578 1 : addMessageListener("Browser:PurgeSessionHistory", function BrowserPurgeHistory() {
579 0 : let sessionHistory = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
580 0 : if (!sessionHistory) {
581 0 : return;
582 : }
583 :
584 : // place the entry at current index at the end of the history list, so it won't get removed
585 0 : if (sessionHistory.index < sessionHistory.count - 1) {
586 0 : let legacy = sessionHistory.legacySHistory;
587 0 : legacy.QueryInterface(Ci.nsISHistoryInternal);
588 0 : let indexEntry = legacy.getEntryAtIndex(sessionHistory.index, false);
589 0 : indexEntry.QueryInterface(Ci.nsISHEntry);
590 0 : legacy.addEntry(indexEntry, true);
591 : }
592 :
593 0 : let purge = sessionHistory.count;
594 0 : if (global.content.location.href != "about:blank") {
595 0 : --purge; // Don't remove the page the user's staring at from shistory
596 : }
597 :
598 0 : if (purge > 0) {
599 0 : sessionHistory.legacySHistory.PurgeHistory(purge);
600 : }
601 0 : });
602 :
603 0 : addMessageListener("ViewSource:GetSelection", SelectionSourceContent);
604 :
605 0 : addEventListener("MozApplicationManifest", function(e) {
606 0 : let doc = e.target;
607 0 : let info = {
608 0 : uri: doc.documentURI,
609 0 : characterSet: doc.characterSet,
610 0 : manifest: doc.documentElement.getAttribute("manifest"),
611 0 : principal: doc.nodePrincipal,
612 : };
613 0 : sendAsyncMessage("MozApplicationManifest", info);
614 0 : }, false);
615 :
616 1 : let AutoCompletePopup = {
617 1 : QueryInterface: ChromeUtils.generateQI([Ci.nsIAutoCompletePopup]),
618 :
619 1 : _connected: false,
620 :
621 1 : MESSAGES: [
622 0 : "FormAutoComplete:HandleEnter",
623 1 : "FormAutoComplete:PopupClosed",
624 1 : "FormAutoComplete:PopupOpened",
625 0 : "FormAutoComplete:RequestFocus",
626 : ],
627 :
628 1 : init() {
629 1 : addEventListener("unload", this);
630 0 : addEventListener("DOMContentLoaded", this);
631 : // WebExtension browserAction is preloaded and does not receive DCL, wait
632 : // on pageshow so we can hookup the formfill controller.
633 0 : addEventListener("pageshow", this, true);
634 :
635 0 : for (let messageName of this.MESSAGES) {
636 4 : addMessageListener(messageName, this);
637 : }
638 :
639 0 : this._input = null;
640 0 : this._popupOpen = false;
641 : },
642 :
643 0 : destroy() {
644 1 : if (this._connected) {
645 0 : let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
646 0 : .getService(Ci.nsIFormFillController);
647 0 : controller.detachFromBrowser(docShell);
648 0 : this._connected = false;
649 : }
650 :
651 1 : removeEventListener("pageshow", this);
652 1 : removeEventListener("unload", this);
653 1 : removeEventListener("DOMContentLoaded", this);
654 :
655 0 : for (let messageName of this.MESSAGES) {
656 0 : removeMessageListener(messageName, this);
657 : }
658 : },
659 :
660 1 : connect() {
661 0 : if (this._connected) {
662 0 : return;
663 : }
664 : // We need to wait for a content viewer to be available
665 : // before we can attach our AutoCompletePopup handler,
666 : // since nsFormFillController assumes one will exist
667 : // when we call attachToBrowser.
668 :
669 : // Hook up the form fill autocomplete controller.
670 0 : let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
671 0 : .getService(Ci.nsIFormFillController);
672 0 : controller.attachToBrowser(docShell,
673 0 : this.QueryInterface(Ci.nsIAutoCompletePopup));
674 0 : this._connected = true;
675 0 : },
676 :
677 0 : handleEvent(event) {
678 0 : switch (event.type) {
679 : case "pageshow": {
680 0 : removeEventListener("pageshow", this);
681 0 : this.connect();
682 : break;
683 : }
684 :
685 : case "DOMContentLoaded": {
686 0 : removeEventListener("DOMContentLoaded", this);
687 0 : this.connect();
688 : break;
689 : }
690 :
691 : case "unload": {
692 1 : this.destroy();
693 : break;
694 : }
695 : }
696 : },
697 :
698 1 : receiveMessage(message) {
699 0 : switch (message.name) {
700 0 : case "FormAutoComplete:HandleEnter": {
701 0 : this.selectedIndex = message.data.selectedIndex;
702 :
703 0 : let controller = Cc["@mozilla.org/autocomplete/controller;1"]
704 0 : .getService(Ci.nsIAutoCompleteController);
705 0 : controller.handleEnter(message.data.isPopupSelection);
706 0 : break;
707 : }
708 :
709 : case "FormAutoComplete:PopupClosed": {
710 0 : this._popupOpen = false;
711 : break;
712 : }
713 :
714 : case "FormAutoComplete:PopupOpened": {
715 0 : this._popupOpen = true;
716 : break;
717 : }
718 :
719 : case "FormAutoComplete:RequestFocus": {
720 0 : if (this._input) {
721 0 : this._input.focus();
722 : }
723 : break;
724 : }
725 : }
726 : },
727 :
728 1 : get input() { return this._input; },
729 1 : get overrideValue() { return null; },
730 0 : set selectedIndex(index) {
731 0 : sendAsyncMessage("FormAutoComplete:SetSelectedIndex", { index });
732 : },
733 1 : get selectedIndex() {
734 : // selectedIndex getter must be synchronous because we need the
735 : // correct value when the controller is in controller::HandleEnter.
736 : // We can't easily just let the parent inform us the new value every
737 : // time it changes because not every action that can change the
738 : // selectedIndex is trivial to catch (e.g. moving the mouse over the
739 : // list).
740 0 : return sendSyncMessage("FormAutoComplete:GetSelectedIndex", {});
741 : },
742 1 : get popupOpen() {
743 0 : return this._popupOpen;
744 : },
745 :
746 0 : openAutocompletePopup(input, element) {
747 0 : if (this._popupOpen || !input) {
748 0 : return;
749 : }
750 :
751 0 : let rect = BrowserUtils.getElementBoundingScreenRect(element);
752 0 : let window = element.ownerGlobal;
753 0 : let dir = window.getComputedStyle(element).direction;
754 0 : let results = this.getResultsFromController(input);
755 :
756 0 : sendAsyncMessage("FormAutoComplete:MaybeOpenPopup",
757 0 : { results, rect, dir });
758 0 : this._input = input;
759 0 : },
760 :
761 1 : closePopup() {
762 : // We set this here instead of just waiting for the
763 : // PopupClosed message to do it so that we don't end
764 : // up in a state where the content thinks that a popup
765 : // is open when it isn't (or soon won't be).
766 0 : this._popupOpen = false;
767 0 : sendAsyncMessage("FormAutoComplete:ClosePopup", {});
768 : },
769 :
770 0 : invalidate() {
771 0 : if (this._popupOpen) {
772 0 : let results = this.getResultsFromController(this._input);
773 0 : sendAsyncMessage("FormAutoComplete:Invalidate", { results });
774 : }
775 : },
776 :
777 0 : selectBy(reverse, page) {
778 0 : this._index = sendSyncMessage("FormAutoComplete:SelectBy", {
779 0 : reverse,
780 0 : page
781 : });
782 : },
783 :
784 1 : getResultsFromController(inputField) {
785 0 : let results = [];
786 :
787 0 : if (!inputField) {
788 0 : return results;
789 : }
790 :
791 0 : let controller = inputField.controller;
792 0 : if (!(controller instanceof Ci.nsIAutoCompleteController)) {
793 0 : return results;
794 : }
795 :
796 0 : for (let i = 0; i < controller.matchCount; ++i) {
797 0 : let result = {};
798 0 : result.value = controller.getValueAt(i);
799 0 : result.label = controller.getLabelAt(i);
800 0 : result.comment = controller.getCommentAt(i);
801 0 : result.style = controller.getStyleAt(i);
802 0 : result.image = controller.getImageAt(i);
803 0 : results.push(result);
804 : }
805 :
806 0 : return results;
807 0 : },
808 : };
809 :
810 1 : AutoCompletePopup.init();
811 :
812 1 : addEventListener("mozshowdropdown", event => {
813 0 : if (!event.isTrusted)
814 0 : return;
815 :
816 0 : if (!SelectContentHelper.open) {
817 0 : new SelectContentHelper(event.target, {isOpenedViaTouch: false}, this);
818 : }
819 : });
820 :
821 1 : addEventListener("mozshowdropdown-sourcetouch", event => {
822 0 : if (!event.isTrusted)
823 0 : return;
824 :
825 0 : if (!SelectContentHelper.open) {
826 0 : new SelectContentHelper(event.target, {isOpenedViaTouch: true}, this);
827 : }
828 : });
829 :
830 1 : let ExtFind = {
831 1 : init() {
832 1 : addMessageListener("ext-Finder:CollectResults", this);
833 1 : addMessageListener("ext-Finder:HighlightResults", this);
834 1 : addMessageListener("ext-Finder:clearHighlighting", this);
835 : },
836 :
837 1 : _findContent: null,
838 :
839 1 : async receiveMessage(message) {
840 0 : if (!this._findContent) {
841 0 : this._findContent = new FindContent(docShell);
842 : }
843 :
844 0 : let data;
845 0 : switch (message.name) {
846 : case "ext-Finder:CollectResults":
847 0 : this.finderInited = true;
848 0 : data = await this._findContent.findRanges(message.data);
849 0 : sendAsyncMessage("ext-Finder:CollectResultsFinished", data);
850 : break;
851 : case "ext-Finder:HighlightResults":
852 0 : data = this._findContent.highlightResults(message.data);
853 0 : sendAsyncMessage("ext-Finder:HighlightResultsFinished", data);
854 : break;
855 : case "ext-Finder:clearHighlighting":
856 0 : this._findContent.highlighter.highlight(false);
857 : break;
858 : }
859 : },
860 : };
861 :
862 1 : ExtFind.init();
|