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 var EXPORTED_SYMBOLS = ["ClipboardReadPasteParent"];
8 const kMenuPopupId = "clipboardReadPasteMenuPopup";
10 // Exchanges messages with the child actor and handles events from the
12 class ClipboardReadPasteParent extends JSWindowActorParent {
16 this._menupopup = null;
17 this._menuitem = null;
18 this._delayTimer = null;
19 this._pasteMenuItemClicked = false;
20 this._lastBeepTime = 0;
24 if (this._menupopup) {
25 this._menupopup.hidePopup(true);
29 // EventListener interface.
31 switch (aEvent.type) {
41 this.onKeyDown(aEvent);
48 this._pasteMenuItemClicked = true;
49 this.sendAsyncMessage("ClipboardReadPaste:PasteMenuItemClicked");
53 // Remove the listeners before potentially sending the async message
54 // below, because that might throw.
55 this._removeMenupopupEventListeners();
56 this._clearDelayTimer();
57 this._stopWatchingForSpammyActivation();
59 if (this._pasteMenuItemClicked) {
60 // A message was already sent. Reset the state to handle further
61 // click/dismiss events properly.
62 this._pasteMenuItemClicked = false;
64 this.sendAsyncMessage("ClipboardReadPaste:PasteMenuItemDismissed");
69 if (!this._menuitem.disabled) {
73 let accesskey = this._menuitem.getAttribute("accesskey");
75 aEvent.key == accesskey.toLowerCase() ||
76 aEvent.key == accesskey.toUpperCase()
78 if (Date.now() - this._lastBeepTime > 1000) {
79 Cc["@mozilla.org/sound;1"].getService(Ci.nsISound).beep();
80 this._lastBeepTime = Date.now();
82 this._refreshDelayTimer();
86 // For JSWindowActorParent.
87 receiveMessage(value) {
88 if (value.name == "ClipboardReadPaste:ShowMenupopup") {
89 if (!this._menupopup) {
90 this._menupopup = this._getMenupopup();
91 this._menuitem = this._menupopup.firstElementChild;
94 this._addMenupopupEventListeners();
96 const browser = this.browsingContext.top.embedderElement;
97 const window = browser.ownerGlobal;
98 const windowUtils = window.windowUtils;
100 let mouseXInCSSPixels = {};
101 let mouseYInCSSPixels = {};
102 windowUtils.getLastOverWindowPointerLocationInCSSPixels(
107 this._menuitem.disabled = true;
108 this._startWatchingForSpammyActivation();
109 // `openPopup` is a no-op if the popup is already opened.
110 // That property is used when `navigator.clipboard.readText()` or
111 // `navigator.clipboard.read()`is called from two different frames, e.g.
112 // an iframe and the top level frame. In that scenario, the two frames
113 // correspond to different `navigator.clipboard` instances. When
114 // `readText()` or `read()` is called from both frames, an actor pair is
115 // instantiated for each of them. Both actor parents will call `openPopup`
116 // on the same `_menupopup` object. If the popup is already open,
117 // `openPopup` is a no-op. When the popup is clicked or dismissed both
118 // actor parents will receive the corresponding event.
119 this._menupopup.openPopup(
121 "overlap" /* options */,
122 mouseXInCSSPixels.value,
123 mouseYInCSSPixels.value,
124 true /* isContextMenu */
127 this._refreshDelayTimer();
131 _addMenupopupEventListeners() {
132 this._menupopup.addEventListener("command", this);
133 this._menupopup.addEventListener("popuphiding", this);
136 _removeMenupopupEventListeners() {
137 this._menupopup.removeEventListener("command", this);
138 this._menupopup.removeEventListener("popuphiding", this);
141 _createMenupopup(aChromeDoc) {
142 let menuitem = aChromeDoc.createXULElement("menuitem");
143 menuitem.id = "clipboardReadPasteMenuItem";
144 menuitem.setAttribute("data-l10n-id", "text-action-paste");
146 let menupopup = aChromeDoc.createXULElement("menupopup");
147 menupopup.id = kMenuPopupId;
148 menupopup.appendChild(menuitem);
153 let browser = this.browsingContext.top.embedderElement;
154 let window = browser.ownerGlobal;
155 let chromeDoc = window.document;
157 let menupopup = chromeDoc.getElementById(kMenuPopupId);
158 if (menupopup == null) {
159 menupopup = this._createMenupopup(chromeDoc);
161 chromeDoc.querySelector("popupset") || chromeDoc.documentElement;
162 parent.appendChild(menupopup);
168 _startWatchingForSpammyActivation() {
169 let doc = this._menuitem.ownerDocument;
170 Services.els.addSystemEventListener(doc, "keydown", this, true);
173 _stopWatchingForSpammyActivation() {
174 let doc = this._menuitem.ownerDocument;
175 Services.els.removeSystemEventListener(doc, "keydown", this, true);
179 if (this._delayTimer) {
180 let window = this._menuitem.ownerGlobal;
181 window.clearTimeout(this._delayTimer);
182 this._delayTimer = null;
186 _refreshDelayTimer() {
187 this._clearDelayTimer();
189 let window = this._menuitem.ownerGlobal;
190 let delay = Services.prefs.getIntPref("security.dialog_enable_delay");
191 this._delayTimer = window.setTimeout(() => {
192 this._menuitem.disabled = false;
193 this._stopWatchingForSpammyActivation();
194 this._delayTimer = null;