2014-06-25 09:12:07 +04:00
|
|
|
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
2014-03-14 22:45:53 +04:00
|
|
|
/* 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/. */
|
|
|
|
|
2017-02-07 13:52:06 +03:00
|
|
|
/* eslint-env mozilla/frame-script */
|
|
|
|
|
2015-09-15 21:19:45 +03:00
|
|
|
var Cc = Components.classes;
|
|
|
|
var Ci = Components.interfaces;
|
|
|
|
var Cu = Components.utils;
|
|
|
|
var Cr = Components.results;
|
2014-03-14 22:45:53 +04:00
|
|
|
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
2014-10-28 17:58:07 +03:00
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
2014-03-14 22:45:53 +04:00
|
|
|
|
2016-06-02 16:29:00 +03:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
|
|
|
|
"resource://gre/modules/ReaderMode.jsm");
|
2016-08-18 17:50:58 +03:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
|
|
|
|
"resource://gre/modules/BrowserUtils.jsm");
|
2016-12-21 19:09:39 +03:00
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "SelectContentHelper",
|
|
|
|
"resource://gre/modules/SelectContentHelper.jsm");
|
2016-06-02 16:29:00 +03:00
|
|
|
|
2014-03-14 22:45:53 +04:00
|
|
|
var global = this;
|
|
|
|
|
2015-04-13 23:23:51 +03:00
|
|
|
|
|
|
|
// Lazily load the finder code
|
2016-11-12 02:22:34 +03:00
|
|
|
addMessageListener("Finder:Initialize", function() {
|
2015-04-13 23:23:51 +03:00
|
|
|
let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {});
|
|
|
|
new RemoteFinderListener(global);
|
|
|
|
});
|
|
|
|
|
2015-09-15 21:19:45 +03:00
|
|
|
var ClickEventHandler = {
|
2014-03-14 22:45:53 +04:00
|
|
|
init: function init() {
|
|
|
|
this._scrollable = null;
|
|
|
|
this._scrolldir = "";
|
|
|
|
this._startX = null;
|
|
|
|
this._startY = null;
|
|
|
|
this._screenX = null;
|
|
|
|
this._screenY = null;
|
|
|
|
this._lastFrame = null;
|
2015-07-14 22:28:57 +03:00
|
|
|
this.autoscrollLoop = this.autoscrollLoop.bind(this);
|
2014-03-14 22:45:53 +04:00
|
|
|
|
2015-03-11 23:22:09 +03:00
|
|
|
Services.els.addSystemEventListener(global, "mousedown", this, true);
|
2014-03-14 22:45:53 +04:00
|
|
|
|
|
|
|
addMessageListener("Autoscroll:Stop", this);
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
isAutoscrollBlocker(node) {
|
2014-03-14 22:45:53 +04:00
|
|
|
let mmPaste = Services.prefs.getBoolPref("middlemouse.paste");
|
|
|
|
let mmScrollbarPosition = Services.prefs.getBoolPref("middlemouse.scrollbarPosition");
|
|
|
|
|
|
|
|
while (node) {
|
|
|
|
if ((node instanceof content.HTMLAnchorElement || node instanceof content.HTMLAreaElement) &&
|
|
|
|
node.hasAttribute("href")) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mmPaste && (node instanceof content.HTMLInputElement ||
|
|
|
|
node instanceof content.HTMLTextAreaElement)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node instanceof content.XULElement && mmScrollbarPosition
|
|
|
|
&& (node.localName == "scrollbar" || node.localName == "scrollcorner")) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
node = node.parentNode;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
findNearestScrollableElement(aNode) {
|
2014-03-14 22:45:53 +04:00
|
|
|
// this is a list of overflow property values that allow scrolling
|
2017-01-17 18:48:17 +03:00
|
|
|
const scrollingAllowed = ["scroll", "auto"];
|
2014-03-14 22:45:53 +04:00
|
|
|
|
|
|
|
// go upward in the DOM and find any parent element that has a overflow
|
|
|
|
// area and can therefore be scrolled
|
2014-07-23 15:57:54 +04:00
|
|
|
for (this._scrollable = aNode; this._scrollable;
|
2014-03-14 22:45:53 +04:00
|
|
|
this._scrollable = this._scrollable.parentNode) {
|
|
|
|
// do not use overflow based autoscroll for <html> and <body>
|
|
|
|
// Elements or non-html elements such as svg or Document nodes
|
|
|
|
// also make sure to skip select elements that are not multiline
|
|
|
|
if (!(this._scrollable instanceof content.HTMLElement) ||
|
|
|
|
((this._scrollable instanceof content.HTMLSelectElement) && !this._scrollable.multiple)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-01-27 12:51:03 +03:00
|
|
|
var overflowx = this._scrollable.ownerGlobal
|
2017-01-27 12:51:02 +03:00
|
|
|
.getComputedStyle(this._scrollable)
|
2017-01-17 18:48:17 +03:00
|
|
|
.getPropertyValue("overflow-x");
|
2017-01-27 12:51:03 +03:00
|
|
|
var overflowy = this._scrollable.ownerGlobal
|
2017-01-27 12:51:02 +03:00
|
|
|
.getComputedStyle(this._scrollable)
|
2017-01-17 18:48:17 +03:00
|
|
|
.getPropertyValue("overflow-y");
|
2014-03-14 22:45:53 +04:00
|
|
|
// we already discarded non-multiline selects so allow vertical
|
|
|
|
// scroll for multiline ones directly without checking for a
|
|
|
|
// overflow property
|
|
|
|
var scrollVert = this._scrollable.scrollTopMax &&
|
|
|
|
(this._scrollable instanceof content.HTMLSelectElement ||
|
|
|
|
scrollingAllowed.indexOf(overflowy) >= 0);
|
|
|
|
|
|
|
|
// do not allow horizontal scrolling for select elements, it leads
|
|
|
|
// to visual artifacts and is not the expected behavior anyway
|
|
|
|
if (!(this._scrollable instanceof content.HTMLSelectElement) &&
|
2015-10-27 07:38:02 +03:00
|
|
|
this._scrollable.scrollLeftMin != this._scrollable.scrollLeftMax &&
|
2014-03-14 22:45:53 +04:00
|
|
|
scrollingAllowed.indexOf(overflowx) >= 0) {
|
|
|
|
this._scrolldir = scrollVert ? "NSEW" : "EW";
|
|
|
|
break;
|
|
|
|
} else if (scrollVert) {
|
|
|
|
this._scrolldir = "NS";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this._scrollable) {
|
2017-01-27 12:51:03 +03:00
|
|
|
this._scrollable = aNode.ownerGlobal;
|
2015-10-27 07:38:02 +03:00
|
|
|
if (this._scrollable.scrollMaxX != this._scrollable.scrollMinX) {
|
|
|
|
this._scrolldir = this._scrollable.scrollMaxY !=
|
|
|
|
this._scrollable.scrollMinY ? "NSEW" : "EW";
|
|
|
|
} else if (this._scrollable.scrollMaxY != this._scrollable.scrollMinY) {
|
2014-03-14 22:45:53 +04:00
|
|
|
this._scrolldir = "NS";
|
2014-07-23 15:57:54 +04:00
|
|
|
} else if (this._scrollable.frameElement) {
|
|
|
|
this.findNearestScrollableElement(this._scrollable.frameElement);
|
2014-03-14 22:45:53 +04:00
|
|
|
} else {
|
|
|
|
this._scrollable = null; // abort scrolling
|
|
|
|
}
|
|
|
|
}
|
2014-07-23 15:57:54 +04:00
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
startScroll(event) {
|
2014-07-23 15:57:54 +04:00
|
|
|
|
|
|
|
this.findNearestScrollableElement(event.originalTarget);
|
|
|
|
|
|
|
|
if (!this._scrollable)
|
|
|
|
return;
|
2014-03-14 22:45:53 +04:00
|
|
|
|
2014-03-18 19:00:33 +04:00
|
|
|
let [enabled] = sendSyncMessage("Autoscroll:Start",
|
|
|
|
{scrolldir: this._scrolldir,
|
|
|
|
screenX: event.screenX,
|
|
|
|
screenY: event.screenY});
|
|
|
|
if (!enabled) {
|
|
|
|
this._scrollable = null;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-03-11 23:22:09 +03:00
|
|
|
Services.els.addSystemEventListener(global, "mousemove", this, true);
|
2014-03-14 22:45:53 +04:00
|
|
|
addEventListener("pagehide", this, true);
|
|
|
|
|
|
|
|
this._ignoreMouseEvents = true;
|
|
|
|
this._startX = event.screenX;
|
|
|
|
this._startY = event.screenY;
|
|
|
|
this._screenX = event.screenX;
|
|
|
|
this._screenY = event.screenY;
|
|
|
|
this._scrollErrorX = 0;
|
|
|
|
this._scrollErrorY = 0;
|
2015-07-14 22:28:57 +03:00
|
|
|
this._lastFrame = content.performance.now();
|
2014-03-14 22:45:53 +04:00
|
|
|
|
2015-07-14 22:28:57 +03:00
|
|
|
content.requestAnimationFrame(this.autoscrollLoop);
|
2014-03-14 22:45:53 +04:00
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
stopScroll() {
|
2014-03-14 22:45:53 +04:00
|
|
|
if (this._scrollable) {
|
2015-02-20 03:03:07 +03:00
|
|
|
this._scrollable.mozScrollSnap();
|
2014-03-14 22:45:53 +04:00
|
|
|
this._scrollable = null;
|
|
|
|
|
2015-03-11 23:22:09 +03:00
|
|
|
Services.els.removeSystemEventListener(global, "mousemove", this, true);
|
2014-03-14 22:45:53 +04:00
|
|
|
removeEventListener("pagehide", this, true);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
accelerate(curr, start) {
|
2014-03-14 22:45:53 +04:00
|
|
|
const speed = 12;
|
|
|
|
var val = (curr - start) / speed;
|
|
|
|
|
|
|
|
if (val > 1)
|
|
|
|
return val * Math.sqrt(val) - 1;
|
|
|
|
if (val < -1)
|
|
|
|
return val * Math.sqrt(-val) + 1;
|
|
|
|
return 0;
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
roundToZero(num) {
|
2014-03-14 22:45:53 +04:00
|
|
|
if (num > 0)
|
|
|
|
return Math.floor(num);
|
|
|
|
return Math.ceil(num);
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
autoscrollLoop(timestamp) {
|
2014-03-14 22:45:53 +04:00
|
|
|
if (!this._scrollable) {
|
|
|
|
// Scrolling has been canceled
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// avoid long jumps when the browser hangs for more than
|
|
|
|
// |maxTimeDelta| ms
|
|
|
|
const maxTimeDelta = 100;
|
|
|
|
var timeDelta = Math.min(maxTimeDelta, timestamp - this._lastFrame);
|
|
|
|
// we used to scroll |accelerate()| pixels every 20ms (50fps)
|
|
|
|
var timeCompensation = timeDelta / 20;
|
|
|
|
this._lastFrame = timestamp;
|
|
|
|
|
|
|
|
var actualScrollX = 0;
|
|
|
|
var actualScrollY = 0;
|
|
|
|
// don't bother scrolling vertically when the scrolldir is only horizontal
|
|
|
|
// and the other way around
|
2017-01-17 18:48:17 +03:00
|
|
|
if (this._scrolldir != "EW") {
|
2014-03-14 22:45:53 +04:00
|
|
|
var y = this.accelerate(this._screenY, this._startY) * timeCompensation;
|
|
|
|
var desiredScrollY = this._scrollErrorY + y;
|
|
|
|
actualScrollY = this.roundToZero(desiredScrollY);
|
|
|
|
this._scrollErrorY = (desiredScrollY - actualScrollY);
|
|
|
|
}
|
2017-01-17 18:48:17 +03:00
|
|
|
if (this._scrolldir != "NS") {
|
2014-03-14 22:45:53 +04:00
|
|
|
var x = this.accelerate(this._screenX, this._startX) * timeCompensation;
|
|
|
|
var desiredScrollX = this._scrollErrorX + x;
|
|
|
|
actualScrollX = this.roundToZero(desiredScrollX);
|
|
|
|
this._scrollErrorX = (desiredScrollX - actualScrollX);
|
|
|
|
}
|
|
|
|
|
2016-02-06 02:01:27 +03:00
|
|
|
const kAutoscroll = 15; // defined in mozilla/layers/ScrollInputMethods.h
|
|
|
|
Services.telemetry.getHistogramById("SCROLL_INPUT_METHODS").add(kAutoscroll);
|
|
|
|
|
2016-10-18 10:46:30 +03:00
|
|
|
this._scrollable.scrollBy({
|
|
|
|
left: actualScrollX,
|
|
|
|
top: actualScrollY,
|
|
|
|
behavior: "instant"
|
|
|
|
});
|
2015-07-14 22:28:57 +03:00
|
|
|
content.requestAnimationFrame(this.autoscrollLoop);
|
2014-03-14 22:45:53 +04:00
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
handleEvent(event) {
|
2014-03-14 22:45:53 +04:00
|
|
|
if (event.type == "mousemove") {
|
|
|
|
this._screenX = event.screenX;
|
|
|
|
this._screenY = event.screenY;
|
|
|
|
} else if (event.type == "mousedown") {
|
|
|
|
if (event.isTrusted &
|
|
|
|
!event.defaultPrevented &&
|
|
|
|
event.button == 1 &&
|
|
|
|
!this._scrollable &&
|
|
|
|
!this.isAutoscrollBlocker(event.originalTarget)) {
|
|
|
|
this.startScroll(event);
|
|
|
|
}
|
|
|
|
} else if (event.type == "pagehide") {
|
|
|
|
if (this._scrollable) {
|
|
|
|
var doc =
|
|
|
|
this._scrollable.ownerDocument || this._scrollable.document;
|
|
|
|
if (doc == event.target) {
|
|
|
|
sendAsyncMessage("Autoscroll:Cancel");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
receiveMessage(msg) {
|
2014-03-14 22:45:53 +04:00
|
|
|
switch (msg.name) {
|
|
|
|
case "Autoscroll:Stop": {
|
|
|
|
this.stopScroll();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
|
|
|
ClickEventHandler.init();
|
2014-03-21 03:31:20 +04:00
|
|
|
|
2015-09-15 21:19:45 +03:00
|
|
|
var PopupBlocking = {
|
2014-03-21 03:31:20 +04:00
|
|
|
popupData: null,
|
|
|
|
popupDataInternal: null,
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
init() {
|
2014-03-21 03:31:20 +04:00
|
|
|
addEventListener("DOMPopupBlocked", this, true);
|
|
|
|
addEventListener("pageshow", this, true);
|
|
|
|
addEventListener("pagehide", this, true);
|
|
|
|
|
|
|
|
addMessageListener("PopupBlocking:UnblockPopup", this);
|
2016-05-31 01:02:17 +03:00
|
|
|
addMessageListener("PopupBlocking:GetBlockedPopupList", this);
|
2014-03-21 03:31:20 +04:00
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
receiveMessage(msg) {
|
2014-03-21 03:31:20 +04:00
|
|
|
switch (msg.name) {
|
|
|
|
case "PopupBlocking:UnblockPopup": {
|
|
|
|
let i = msg.data.index;
|
|
|
|
if (this.popupData && this.popupData[i]) {
|
|
|
|
let data = this.popupData[i];
|
|
|
|
let internals = this.popupDataInternal[i];
|
|
|
|
let dwi = internals.requestingWindow;
|
|
|
|
|
|
|
|
// If we have a requesting window and the requesting document is
|
|
|
|
// still the current document, open the popup.
|
|
|
|
if (dwi && dwi.document == internals.requestingDocument) {
|
2016-05-31 01:03:16 +03:00
|
|
|
dwi.open(data.popupWindowURIspec, data.popupWindowName, data.popupWindowFeatures);
|
2014-03-21 03:31:20 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2016-05-31 01:02:17 +03:00
|
|
|
|
|
|
|
case "PopupBlocking:GetBlockedPopupList": {
|
2016-05-31 01:03:16 +03:00
|
|
|
let popupData = [];
|
|
|
|
let length = this.popupData ? this.popupData.length : 0;
|
|
|
|
|
|
|
|
// Limit 15 popup URLs to be reported through the UI
|
|
|
|
length = Math.min(length, 15);
|
|
|
|
|
|
|
|
for (let i = 0; i < length; i++) {
|
|
|
|
let popupWindowURIspec = this.popupData[i].popupWindowURIspec;
|
|
|
|
|
|
|
|
if (popupWindowURIspec == global.content.location.href) {
|
|
|
|
popupWindowURIspec = "<self>";
|
|
|
|
} else {
|
|
|
|
// Limit 500 chars to be sent because the URI will be cropped
|
|
|
|
// by the UI anyway, and data: URIs can be significantly larger.
|
|
|
|
popupWindowURIspec = popupWindowURIspec.substring(0, 500)
|
|
|
|
}
|
|
|
|
|
|
|
|
popupData.push({popupWindowURIspec});
|
|
|
|
}
|
|
|
|
|
|
|
|
sendAsyncMessage("PopupBlocking:ReplyGetBlockedPopupList", {popupData});
|
2016-05-31 01:02:17 +03:00
|
|
|
break;
|
|
|
|
}
|
2014-03-21 03:31:20 +04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
handleEvent(ev) {
|
2014-03-21 03:31:20 +04:00
|
|
|
switch (ev.type) {
|
|
|
|
case "DOMPopupBlocked":
|
|
|
|
return this.onPopupBlocked(ev);
|
|
|
|
case "pageshow":
|
|
|
|
return this.onPageShow(ev);
|
|
|
|
case "pagehide":
|
|
|
|
return this.onPageHide(ev);
|
|
|
|
}
|
2016-04-05 21:33:48 +03:00
|
|
|
return undefined;
|
2014-03-21 03:31:20 +04:00
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
onPopupBlocked(ev) {
|
2014-03-21 03:31:20 +04:00
|
|
|
if (!this.popupData) {
|
|
|
|
this.popupData = new Array();
|
|
|
|
this.popupDataInternal = new Array();
|
|
|
|
}
|
|
|
|
|
|
|
|
let obj = {
|
2016-05-31 01:03:16 +03:00
|
|
|
popupWindowURIspec: ev.popupWindowURI ? ev.popupWindowURI.spec : "about:blank",
|
2014-03-21 03:31:20 +04:00
|
|
|
popupWindowFeatures: ev.popupWindowFeatures,
|
|
|
|
popupWindowName: ev.popupWindowName
|
|
|
|
};
|
|
|
|
|
|
|
|
let internals = {
|
|
|
|
requestingWindow: ev.requestingWindow,
|
|
|
|
requestingDocument: ev.requestingWindow.document,
|
|
|
|
};
|
|
|
|
|
|
|
|
this.popupData.push(obj);
|
|
|
|
this.popupDataInternal.push(internals);
|
|
|
|
this.updateBlockedPopups(true);
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
onPageShow(ev) {
|
2014-03-21 03:31:20 +04:00
|
|
|
if (this.popupData) {
|
|
|
|
let i = 0;
|
|
|
|
while (i < this.popupData.length) {
|
|
|
|
// Filter out irrelevant reports.
|
|
|
|
if (this.popupDataInternal[i].requestingWindow &&
|
|
|
|
(this.popupDataInternal[i].requestingWindow.document ==
|
|
|
|
this.popupDataInternal[i].requestingDocument)) {
|
|
|
|
i++;
|
|
|
|
} else {
|
|
|
|
this.popupData.splice(i, 1);
|
|
|
|
this.popupDataInternal.splice(i, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this.popupData.length == 0) {
|
|
|
|
this.popupData = null;
|
|
|
|
this.popupDataInternal = null;
|
|
|
|
}
|
|
|
|
this.updateBlockedPopups(false);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
onPageHide(ev) {
|
2014-03-21 03:31:20 +04:00
|
|
|
if (this.popupData) {
|
|
|
|
this.popupData = null;
|
|
|
|
this.popupDataInternal = null;
|
|
|
|
this.updateBlockedPopups(false);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
updateBlockedPopups(freshPopup) {
|
2014-03-21 03:31:20 +04:00
|
|
|
sendAsyncMessage("PopupBlocking:UpdateBlockedPopups",
|
2016-05-31 01:02:17 +03:00
|
|
|
{
|
|
|
|
count: this.popupData ? this.popupData.length : 0,
|
|
|
|
freshPopup
|
|
|
|
});
|
2014-03-21 03:31:20 +04:00
|
|
|
},
|
|
|
|
};
|
2014-10-07 22:46:25 +04:00
|
|
|
PopupBlocking.init();
|
|
|
|
|
2015-06-17 02:35:52 +03:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "console", () => {
|
|
|
|
// Set up console.* for frame scripts.
|
2015-10-15 13:45:22 +03:00
|
|
|
let Console = Components.utils.import("resource://gre/modules/Console.jsm", {});
|
2015-06-17 02:35:52 +03:00
|
|
|
return new Console.ConsoleAPI();
|
|
|
|
});
|
2014-10-28 17:58:07 +03:00
|
|
|
|
2015-09-15 21:19:45 +03:00
|
|
|
var Printing = {
|
2014-10-28 17:58:07 +03:00
|
|
|
// Bug 1088061: nsPrintEngine's DoCommonPrint currently expects the
|
|
|
|
// progress listener passed to it to QI to an nsIPrintingPromptService
|
|
|
|
// in order to know that a printing progress dialog has been shown. That's
|
|
|
|
// really all the interface is used for, hence the fact that I don't actually
|
|
|
|
// implement the interface here. Bug 1088061 has been filed to remove
|
|
|
|
// this hackery.
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
|
|
|
Ci.nsIPrintingPromptService]),
|
|
|
|
|
|
|
|
MESSAGES: [
|
|
|
|
"Printing:Preview:Enter",
|
|
|
|
"Printing:Preview:Exit",
|
|
|
|
"Printing:Preview:Navigate",
|
2016-06-02 16:29:00 +03:00
|
|
|
"Printing:Preview:ParseDocument",
|
2014-10-28 17:58:07 +03:00
|
|
|
"Printing:Preview:UpdatePageCount",
|
|
|
|
"Printing:Print",
|
|
|
|
],
|
|
|
|
|
|
|
|
init() {
|
|
|
|
this.MESSAGES.forEach(msgName => addMessageListener(msgName, this));
|
2015-09-16 23:12:54 +03:00
|
|
|
addEventListener("PrintingError", this, true);
|
2014-10-28 17:58:07 +03:00
|
|
|
},
|
|
|
|
|
2015-03-05 21:16:35 +03:00
|
|
|
get shouldSavePrintSettings() {
|
2017-01-13 22:21:53 +03:00
|
|
|
return Services.prefs.getBoolPref("print.use_global_printsettings") &&
|
|
|
|
Services.prefs.getBoolPref("print.save_print_settings");
|
2015-03-05 21:16:35 +03:00
|
|
|
},
|
|
|
|
|
2015-09-16 23:12:54 +03:00
|
|
|
handleEvent(event) {
|
|
|
|
if (event.type == "PrintingError") {
|
|
|
|
let win = event.target.defaultView;
|
|
|
|
let wbp = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIWebBrowserPrint);
|
|
|
|
let nsresult = event.detail;
|
|
|
|
sendAsyncMessage("Printing:Error", {
|
|
|
|
isPrinting: wbp.doingPrint,
|
2016-12-30 02:34:54 +03:00
|
|
|
nsresult,
|
2015-09-16 23:12:54 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2014-10-28 17:58:07 +03:00
|
|
|
receiveMessage(message) {
|
|
|
|
let data = message.data;
|
2016-08-04 10:28:58 +03:00
|
|
|
switch (message.name) {
|
2014-10-28 17:58:07 +03:00
|
|
|
case "Printing:Preview:Enter": {
|
2017-02-13 15:07:44 +03:00
|
|
|
this.enterPrintPreview(Services.wm.getOuterWindowWithId(data.windowID), data.simplifiedMode, data.defaultPrinterName);
|
2014-10-28 17:58:07 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case "Printing:Preview:Exit": {
|
|
|
|
this.exitPrintPreview();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case "Printing:Preview:Navigate": {
|
|
|
|
this.navigate(data.navType, data.pageNum);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-06-02 16:29:00 +03:00
|
|
|
case "Printing:Preview:ParseDocument": {
|
|
|
|
this.parseDocument(data.URL, Services.wm.getOuterWindowWithId(data.windowID));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2014-10-28 17:58:07 +03:00
|
|
|
case "Printing:Preview:UpdatePageCount": {
|
|
|
|
this.updatePageCount();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case "Printing:Print": {
|
2017-02-13 15:07:44 +03:00
|
|
|
this.print(Services.wm.getOuterWindowWithId(data.windowID), data.simplifiedMode, data.defaultPrinterName);
|
2014-10-28 17:58:07 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-02-13 15:07:44 +03:00
|
|
|
getPrintSettings(defaultPrinterName) {
|
2015-03-05 21:16:48 +03:00
|
|
|
try {
|
|
|
|
let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"]
|
|
|
|
.getService(Ci.nsIPrintSettingsService);
|
|
|
|
|
|
|
|
let printSettings = PSSVC.globalPrintSettings;
|
|
|
|
if (!printSettings.printerName) {
|
2017-02-13 15:07:44 +03:00
|
|
|
printSettings.printerName = defaultPrinterName;
|
2015-03-05 21:16:48 +03:00
|
|
|
}
|
|
|
|
// First get any defaults from the printer
|
|
|
|
PSSVC.initPrintSettingsFromPrinter(printSettings.printerName,
|
|
|
|
printSettings);
|
|
|
|
// now augment them with any values from last time
|
|
|
|
PSSVC.initPrintSettingsFromPrefs(printSettings, true,
|
|
|
|
printSettings.kInitSaveAll);
|
|
|
|
|
|
|
|
return printSettings;
|
2016-08-04 10:28:58 +03:00
|
|
|
} catch (e) {
|
2015-03-05 21:16:48 +03:00
|
|
|
Components.utils.reportError(e);
|
2014-10-28 17:58:07 +03:00
|
|
|
}
|
|
|
|
|
2015-03-05 21:16:48 +03:00
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
2016-06-02 16:29:00 +03:00
|
|
|
parseDocument(URL, contentWindow) {
|
|
|
|
// By using ReaderMode primitives, we parse given document and place the
|
|
|
|
// resulting JS object into the DOM of current browser.
|
|
|
|
let articlePromise = ReaderMode.parseDocument(contentWindow.document).catch(Cu.reportError);
|
2016-11-12 02:22:34 +03:00
|
|
|
articlePromise.then(function(article) {
|
2016-11-01 21:00:00 +03:00
|
|
|
// We make use of a web progress listener in order to know when the content we inject
|
|
|
|
// into the DOM has finished rendering. If our layout engine is still painting, we
|
|
|
|
// will wait for MozAfterPaint event to be fired.
|
|
|
|
let webProgressListener = {
|
2016-12-30 02:34:54 +03:00
|
|
|
onStateChange(webProgress, req, flags, status) {
|
2016-11-01 21:00:00 +03:00
|
|
|
if (flags & Ci.nsIWebProgressListener.STATE_STOP) {
|
|
|
|
webProgress.removeProgressListener(webProgressListener);
|
|
|
|
let domUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
// Here we tell the parent that we have parsed the document successfully
|
|
|
|
// using ReaderMode primitives and we are able to enter on preview mode.
|
|
|
|
if (domUtils.isMozAfterPaintPending) {
|
|
|
|
addEventListener("MozAfterPaint", function onPaint() {
|
|
|
|
removeEventListener("MozAfterPaint", onPaint);
|
|
|
|
sendAsyncMessage("Printing:Preview:ReaderModeReady");
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
sendAsyncMessage("Printing:Preview:ReaderModeReady");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([
|
|
|
|
Ci.nsIWebProgressListener,
|
|
|
|
Ci.nsISupportsWeakReference,
|
|
|
|
Ci.nsIObserver,
|
|
|
|
]),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Here we QI the docShell into a nsIWebProgress passing our web progress listener in.
|
|
|
|
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIWebProgress);
|
|
|
|
webProgress.addProgressListener(webProgressListener, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST);
|
|
|
|
|
2016-06-02 16:29:00 +03:00
|
|
|
content.document.head.innerHTML = "";
|
|
|
|
|
|
|
|
// Set title of document
|
|
|
|
content.document.title = article.title;
|
|
|
|
|
|
|
|
// Set base URI of document. Print preview code will read this value to
|
|
|
|
// populate the URL field in print settings so that it doesn't show
|
|
|
|
// "about:blank" as its URI.
|
|
|
|
let headBaseElement = content.document.createElement("base");
|
|
|
|
headBaseElement.setAttribute("href", URL);
|
|
|
|
content.document.head.appendChild(headBaseElement);
|
|
|
|
|
|
|
|
// Create link element referencing aboutReader.css and append it to head
|
|
|
|
let headStyleElement = content.document.createElement("link");
|
|
|
|
headStyleElement.setAttribute("rel", "stylesheet");
|
|
|
|
headStyleElement.setAttribute("href", "chrome://global/skin/aboutReader.css");
|
|
|
|
headStyleElement.setAttribute("type", "text/css");
|
|
|
|
content.document.head.appendChild(headStyleElement);
|
|
|
|
|
2016-12-13 16:22:00 +03:00
|
|
|
// Create link element referencing simplifyMode.css and append it to head
|
|
|
|
headStyleElement = content.document.createElement("link");
|
|
|
|
headStyleElement.setAttribute("rel", "stylesheet");
|
|
|
|
headStyleElement.setAttribute("href", "chrome://global/content/simplifyMode.css");
|
|
|
|
headStyleElement.setAttribute("type", "text/css");
|
|
|
|
content.document.head.appendChild(headStyleElement);
|
|
|
|
|
2016-06-02 16:29:00 +03:00
|
|
|
content.document.body.innerHTML = "";
|
|
|
|
|
|
|
|
// Create container div (main element) and append it to body
|
|
|
|
let containerElement = content.document.createElement("div");
|
|
|
|
containerElement.setAttribute("id", "container");
|
|
|
|
content.document.body.appendChild(containerElement);
|
|
|
|
|
|
|
|
// Create header div and append it to container
|
|
|
|
let headerElement = content.document.createElement("div");
|
|
|
|
headerElement.setAttribute("id", "reader-header");
|
|
|
|
headerElement.setAttribute("class", "header");
|
|
|
|
containerElement.appendChild(headerElement);
|
|
|
|
|
|
|
|
// Jam the article's title and byline into header div
|
|
|
|
let titleElement = content.document.createElement("h1");
|
|
|
|
titleElement.setAttribute("id", "reader-title");
|
|
|
|
titleElement.textContent = article.title;
|
|
|
|
headerElement.appendChild(titleElement);
|
|
|
|
|
|
|
|
let bylineElement = content.document.createElement("div");
|
|
|
|
bylineElement.setAttribute("id", "reader-credits");
|
|
|
|
bylineElement.setAttribute("class", "credits");
|
|
|
|
bylineElement.textContent = article.byline;
|
|
|
|
headerElement.appendChild(bylineElement);
|
|
|
|
|
|
|
|
// Display header element
|
|
|
|
headerElement.style.display = "block";
|
|
|
|
|
|
|
|
// Create content div and append it to container
|
|
|
|
let contentElement = content.document.createElement("div");
|
|
|
|
contentElement.setAttribute("class", "content");
|
|
|
|
containerElement.appendChild(contentElement);
|
|
|
|
|
|
|
|
// Create style element for content div and import aboutReaderContent.css
|
|
|
|
let controlContentStyle = content.document.createElement("style");
|
|
|
|
controlContentStyle.setAttribute("scoped", "");
|
|
|
|
controlContentStyle.textContent = "@import url(\"chrome://global/skin/aboutReaderContent.css\");";
|
|
|
|
contentElement.appendChild(controlContentStyle);
|
|
|
|
|
|
|
|
// Jam the article's content into content div
|
|
|
|
let readerContent = content.document.createElement("div");
|
|
|
|
readerContent.setAttribute("id", "moz-reader-content");
|
|
|
|
contentElement.appendChild(readerContent);
|
|
|
|
|
2017-01-09 22:27:25 +03:00
|
|
|
let articleUri = Services.io.newURI(article.url);
|
2016-06-02 16:29:00 +03:00
|
|
|
let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
|
|
|
|
let contentFragment = parserUtils.parseFragment(article.content,
|
|
|
|
Ci.nsIParserUtils.SanitizerDropForms | Ci.nsIParserUtils.SanitizerAllowStyle,
|
|
|
|
false, articleUri, readerContent);
|
|
|
|
|
|
|
|
readerContent.appendChild(contentFragment);
|
|
|
|
|
|
|
|
// Display reader content element
|
|
|
|
readerContent.style.display = "block";
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2017-02-13 15:07:44 +03:00
|
|
|
enterPrintPreview(contentWindow, simplifiedMode, defaultPrinterName) {
|
2015-01-06 22:52:01 +03:00
|
|
|
// We'll call this whenever we've finished reflowing the document, or if
|
|
|
|
// we errored out while attempting to print preview (in which case, we'll
|
|
|
|
// notify the parent that we've failed).
|
|
|
|
let notifyEntered = (error) => {
|
|
|
|
removeEventListener("printPreviewUpdate", onPrintPreviewReady);
|
|
|
|
sendAsyncMessage("Printing:Preview:Entered", {
|
|
|
|
failed: !!error,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
let onPrintPreviewReady = () => {
|
|
|
|
notifyEntered();
|
|
|
|
};
|
|
|
|
|
2014-10-28 17:58:07 +03:00
|
|
|
// We have to wait for the print engine to finish reflowing all of the
|
|
|
|
// documents and subdocuments before we can tell the parent to flip to
|
|
|
|
// the print preview UI - otherwise, the print preview UI might ask for
|
|
|
|
// information (like the number of pages in the document) before we have
|
|
|
|
// our PresShells set up.
|
2015-01-06 22:52:01 +03:00
|
|
|
addEventListener("printPreviewUpdate", onPrintPreviewReady);
|
|
|
|
|
|
|
|
try {
|
2017-02-13 15:07:44 +03:00
|
|
|
let printSettings = this.getPrintSettings(defaultPrinterName);
|
2016-06-02 16:29:00 +03:00
|
|
|
|
|
|
|
// If we happen to be on simplified mode, we need to set docURL in order
|
|
|
|
// to generate header/footer content correctly, since simplified tab has
|
|
|
|
// "about:blank" as its URI.
|
|
|
|
if (printSettings && simplifiedMode)
|
|
|
|
printSettings.docURL = contentWindow.document.baseURI;
|
|
|
|
|
2017-01-24 03:19:12 +03:00
|
|
|
// The print preview docshell will be in a different TabGroup,
|
|
|
|
// so we run it in a separate runnable to avoid touching a
|
|
|
|
// different TabGroup in our own runnable.
|
|
|
|
Services.tm.mainThread.dispatch(() => {
|
|
|
|
try {
|
|
|
|
docShell.printPreview.printPreview(printSettings, contentWindow, this);
|
|
|
|
} catch (error) {
|
|
|
|
// This might fail if we, for example, attempt to print a XUL document.
|
|
|
|
// In that case, we inform the parent to bail out of print preview.
|
|
|
|
Components.utils.reportError(error);
|
|
|
|
notifyEntered(error);
|
|
|
|
}
|
|
|
|
}, Ci.nsIThread.DISPATCH_NORMAL);
|
2016-08-04 10:28:58 +03:00
|
|
|
} catch (error) {
|
2015-01-06 22:52:01 +03:00
|
|
|
// This might fail if we, for example, attempt to print a XUL document.
|
|
|
|
// In that case, we inform the parent to bail out of print preview.
|
|
|
|
Components.utils.reportError(error);
|
|
|
|
notifyEntered(error);
|
|
|
|
}
|
2014-10-28 17:58:07 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
exitPrintPreview() {
|
|
|
|
docShell.printPreview.exitPrintPreview();
|
|
|
|
},
|
|
|
|
|
2017-02-13 15:07:44 +03:00
|
|
|
print(contentWindow, simplifiedMode, defaultPrinterName) {
|
|
|
|
let printSettings = this.getPrintSettings(defaultPrinterName);
|
2016-07-11 09:48:00 +03:00
|
|
|
|
|
|
|
// If we happen to be on simplified mode, we need to set docURL in order
|
|
|
|
// to generate header/footer content correctly, since simplified tab has
|
|
|
|
// "about:blank" as its URI.
|
|
|
|
if (printSettings && simplifiedMode) {
|
|
|
|
printSettings.docURL = contentWindow.document.baseURI;
|
|
|
|
}
|
|
|
|
|
2015-02-25 23:45:22 +03:00
|
|
|
try {
|
|
|
|
let print = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIWebBrowserPrint);
|
2016-10-05 07:42:00 +03:00
|
|
|
|
|
|
|
if (print.doingPrintPreview) {
|
|
|
|
this.logKeyedTelemetry("PRINT_DIALOG_OPENED_COUNT", "FROM_PREVIEW");
|
|
|
|
} else {
|
|
|
|
this.logKeyedTelemetry("PRINT_DIALOG_OPENED_COUNT", "FROM_PAGE");
|
|
|
|
}
|
|
|
|
|
2015-02-25 23:45:22 +03:00
|
|
|
print.print(printSettings, null);
|
2016-07-21 20:57:00 +03:00
|
|
|
|
|
|
|
if (print.doingPrintPreview) {
|
|
|
|
if (simplifiedMode) {
|
2016-10-05 07:42:00 +03:00
|
|
|
this.logKeyedTelemetry("PRINT_COUNT", "SIMPLIFIED");
|
2016-07-21 20:57:00 +03:00
|
|
|
} else {
|
2016-10-05 07:42:00 +03:00
|
|
|
this.logKeyedTelemetry("PRINT_COUNT", "WITH_PREVIEW");
|
2016-07-21 20:57:00 +03:00
|
|
|
}
|
|
|
|
} else {
|
2016-10-05 07:42:00 +03:00
|
|
|
this.logKeyedTelemetry("PRINT_COUNT", "WITHOUT_PREVIEW");
|
2016-07-21 20:57:00 +03:00
|
|
|
}
|
2016-08-04 10:28:58 +03:00
|
|
|
} catch (e) {
|
2015-02-25 23:45:22 +03:00
|
|
|
// Pressing cancel is expressed as an NS_ERROR_ABORT return value,
|
|
|
|
// causing an exception to be thrown which we catch here.
|
2015-03-05 21:16:35 +03:00
|
|
|
if (e.result != Cr.NS_ERROR_ABORT) {
|
|
|
|
Cu.reportError(`In Printing:Print:Done handler, got unexpected rv
|
|
|
|
${e.result}.`);
|
2015-09-16 23:12:54 +03:00
|
|
|
sendAsyncMessage("Printing:Error", {
|
|
|
|
isPrinting: true,
|
|
|
|
nsresult: e.result,
|
|
|
|
});
|
2015-03-05 21:16:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.shouldSavePrintSettings) {
|
|
|
|
let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"]
|
|
|
|
.getService(Ci.nsIPrintSettingsService);
|
|
|
|
|
|
|
|
PSSVC.savePrintSettingsToPrefs(printSettings, true,
|
|
|
|
printSettings.kInitSaveAll);
|
|
|
|
PSSVC.savePrintSettingsToPrefs(printSettings, false,
|
|
|
|
printSettings.kInitSavePrinterName);
|
2015-02-25 23:45:22 +03:00
|
|
|
}
|
2014-10-28 17:58:07 +03:00
|
|
|
},
|
|
|
|
|
2016-10-05 07:42:00 +03:00
|
|
|
logKeyedTelemetry(id, key) {
|
|
|
|
let histogram = Services.telemetry.getKeyedHistogramById(id);
|
|
|
|
histogram.add(key);
|
|
|
|
},
|
|
|
|
|
2014-10-28 17:58:07 +03:00
|
|
|
updatePageCount() {
|
|
|
|
let numPages = docShell.printPreview.printPreviewNumPages;
|
|
|
|
sendAsyncMessage("Printing:Preview:UpdatePageCount", {
|
2016-12-30 02:34:54 +03:00
|
|
|
numPages,
|
2014-10-28 17:58:07 +03:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
navigate(navType, pageNum) {
|
|
|
|
docShell.printPreview.printPreviewNavigate(navType, pageNum);
|
|
|
|
},
|
|
|
|
|
|
|
|
/* nsIWebProgressListener for print preview */
|
|
|
|
|
|
|
|
onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
|
|
|
|
sendAsyncMessage("Printing:Preview:StateChange", {
|
|
|
|
stateFlags: aStateFlags,
|
|
|
|
status: aStatus,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
onProgressChange(aWebProgress, aRequest, aCurSelfProgress,
|
|
|
|
aMaxSelfProgress, aCurTotalProgress,
|
|
|
|
aMaxTotalProgress) {
|
|
|
|
sendAsyncMessage("Printing:Preview:ProgressChange", {
|
|
|
|
curSelfProgress: aCurSelfProgress,
|
|
|
|
maxSelfProgress: aMaxSelfProgress,
|
|
|
|
curTotalProgress: aCurTotalProgress,
|
|
|
|
maxTotalProgress: aMaxTotalProgress,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {},
|
|
|
|
onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {},
|
|
|
|
onSecurityChange(aWebProgress, aRequest, aState) {},
|
|
|
|
}
|
|
|
|
Printing.init();
|
|
|
|
|
2015-03-12 00:19:18 +03:00
|
|
|
function SwitchDocumentDirection(aWindow) {
|
|
|
|
// document.dir can also be "auto", in which case it won't change
|
|
|
|
if (aWindow.document.dir == "ltr" || aWindow.document.dir == "") {
|
|
|
|
aWindow.document.dir = "rtl";
|
|
|
|
} else if (aWindow.document.dir == "rtl") {
|
|
|
|
aWindow.document.dir = "ltr";
|
|
|
|
}
|
|
|
|
for (let run = 0; run < aWindow.frames.length; run++) {
|
|
|
|
SwitchDocumentDirection(aWindow.frames[run]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addMessageListener("SwitchDocumentDirection", () => {
|
|
|
|
SwitchDocumentDirection(content.window);
|
|
|
|
});
|
|
|
|
|
2015-09-15 21:19:45 +03:00
|
|
|
var FindBar = {
|
2015-03-11 23:22:09 +03:00
|
|
|
/* Please keep in sync with toolkit/content/widgets/findbar.xml */
|
|
|
|
FIND_NORMAL: 0,
|
|
|
|
FIND_TYPEAHEAD: 1,
|
|
|
|
FIND_LINKS: 2,
|
|
|
|
|
|
|
|
_findMode: 0,
|
|
|
|
|
|
|
|
init() {
|
|
|
|
addMessageListener("Findbar:UpdateState", this);
|
|
|
|
Services.els.addSystemEventListener(global, "keypress", this, false);
|
|
|
|
Services.els.addSystemEventListener(global, "mouseup", this, false);
|
|
|
|
},
|
|
|
|
|
|
|
|
receiveMessage(msg) {
|
|
|
|
switch (msg.name) {
|
|
|
|
case "Findbar:UpdateState":
|
|
|
|
this._findMode = msg.data.findMode;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
handleEvent(event) {
|
|
|
|
switch (event.type) {
|
|
|
|
case "keypress":
|
|
|
|
this._onKeypress(event);
|
|
|
|
break;
|
|
|
|
case "mouseup":
|
|
|
|
this._onMouseup(event);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns whether FAYT can be used for the given event in
|
|
|
|
* the current content state.
|
|
|
|
*/
|
2015-11-04 18:26:40 +03:00
|
|
|
_canAndShouldFastFind() {
|
|
|
|
let should = false;
|
|
|
|
let can = BrowserUtils.canFastFind(content);
|
|
|
|
if (can) {
|
2016-10-24 20:14:19 +03:00
|
|
|
// XXXgijs: why all these shenanigans? Why not use the event's target?
|
2015-11-04 18:26:40 +03:00
|
|
|
let focusedWindow = {};
|
|
|
|
let elt = Services.focus.getFocusedElementForWindow(content, true, focusedWindow);
|
|
|
|
let win = focusedWindow.value;
|
|
|
|
should = BrowserUtils.shouldFastFind(elt, win);
|
|
|
|
}
|
|
|
|
return { can, should }
|
2015-03-11 23:22:09 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
_onKeypress(event) {
|
|
|
|
// Useless keys:
|
|
|
|
if (event.ctrlKey || event.altKey || event.metaKey || event.defaultPrevented) {
|
2016-02-04 00:12:18 +03:00
|
|
|
return undefined;
|
2015-03-11 23:22:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check the focused element etc.
|
2015-11-04 18:26:40 +03:00
|
|
|
let fastFind = this._canAndShouldFastFind();
|
|
|
|
|
|
|
|
// Can we even use find in this page at all?
|
|
|
|
if (!fastFind.can) {
|
2016-02-04 00:12:18 +03:00
|
|
|
return undefined;
|
2015-03-11 23:22:09 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
let fakeEvent = {};
|
|
|
|
for (let k in event) {
|
2015-12-02 18:25:29 +03:00
|
|
|
if (typeof event[k] != "object" && typeof event[k] != "function" &&
|
2015-12-04 18:29:05 +03:00
|
|
|
!(k in content.KeyboardEvent)) {
|
2015-03-11 23:22:09 +03:00
|
|
|
fakeEvent[k] = event[k];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// sendSyncMessage returns an array of the responses from all listeners
|
2015-11-04 18:26:40 +03:00
|
|
|
let rv = sendSyncMessage("Findbar:Keypress", {
|
2016-12-30 02:34:54 +03:00
|
|
|
fakeEvent,
|
2015-11-04 18:26:40 +03:00
|
|
|
shouldFastFind: fastFind.should
|
|
|
|
});
|
2015-03-11 23:22:09 +03:00
|
|
|
if (rv.indexOf(false) !== -1) {
|
|
|
|
event.preventDefault();
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-05 21:33:48 +03:00
|
|
|
return undefined;
|
2015-03-11 23:22:09 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
_onMouseup(event) {
|
|
|
|
if (this._findMode != this.FIND_NORMAL)
|
|
|
|
sendAsyncMessage("Findbar:Mouseup");
|
|
|
|
},
|
|
|
|
};
|
|
|
|
FindBar.init();
|
2015-06-30 21:46:27 +03:00
|
|
|
|
2016-07-13 02:34:41 +03:00
|
|
|
let WebChannelMessageToChromeListener = {
|
|
|
|
// Preference containing the list (space separated) of origins that are
|
|
|
|
// allowed to send non-string values through a WebChannel, mainly for
|
|
|
|
// backwards compatability. See bug 1238128 for more information.
|
|
|
|
URL_WHITELIST_PREF: "webchannel.allowObject.urlWhitelist",
|
|
|
|
|
|
|
|
// Cached list of whitelisted principals, we avoid constructing this if the
|
|
|
|
// value in `_lastWhitelistValue` hasn't changed since we constructed it last.
|
|
|
|
_cachedWhitelist: [],
|
|
|
|
_lastWhitelistValue: "",
|
|
|
|
|
|
|
|
init() {
|
|
|
|
addEventListener("WebChannelMessageToChrome", e => {
|
|
|
|
this._onMessageToChrome(e);
|
|
|
|
}, true, true);
|
|
|
|
},
|
|
|
|
|
|
|
|
_getWhitelistedPrincipals() {
|
|
|
|
let whitelist = Services.prefs.getCharPref(this.URL_WHITELIST_PREF);
|
|
|
|
if (whitelist != this._lastWhitelistValue) {
|
|
|
|
let urls = whitelist.split(/\s+/);
|
|
|
|
this._cachedWhitelist = urls.map(origin =>
|
|
|
|
Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin));
|
|
|
|
}
|
|
|
|
return this._cachedWhitelist;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onMessageToChrome(e) {
|
|
|
|
// If target is window then we want the document principal, otherwise fallback to target itself.
|
|
|
|
let principal = e.target.nodePrincipal ? e.target.nodePrincipal : e.target.document.nodePrincipal;
|
|
|
|
|
|
|
|
if (e.detail) {
|
2017-01-17 18:48:17 +03:00
|
|
|
if (typeof e.detail != "string") {
|
2016-07-13 02:34:41 +03:00
|
|
|
// Check if the principal is one of the ones that's allowed to send
|
2016-12-02 02:46:00 +03:00
|
|
|
// non-string values for e.detail. They're whitelisted by site origin,
|
|
|
|
// so we compare on originNoSuffix in order to avoid other origin attributes
|
|
|
|
// that are not relevant here, such as containers or private browsing.
|
2016-07-13 02:34:41 +03:00
|
|
|
let objectsAllowed = this._getWhitelistedPrincipals().some(whitelisted =>
|
|
|
|
principal.originNoSuffix == whitelisted.originNoSuffix);
|
|
|
|
if (!objectsAllowed) {
|
|
|
|
Cu.reportError("WebChannelMessageToChrome sent with an object from a non-whitelisted principal");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sendAsyncMessage("WebChannelMessageToChrome", e.detail, { eventTarget: e.target }, principal);
|
2016-12-31 05:47:25 +03:00
|
|
|
} else {
|
2016-07-13 02:34:41 +03:00
|
|
|
Cu.reportError("WebChannel message failed. No message detail.");
|
|
|
|
}
|
2015-06-30 21:46:27 +03:00
|
|
|
}
|
2016-07-13 02:34:41 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
WebChannelMessageToChromeListener.init();
|
2015-06-30 21:46:27 +03:00
|
|
|
|
|
|
|
// This should be kept in sync with /browser/base/content.js.
|
|
|
|
// Add message listener for "WebChannelMessageToContent" messages from chrome scripts.
|
2016-11-12 02:22:34 +03:00
|
|
|
addMessageListener("WebChannelMessageToContent", function(e) {
|
2015-06-30 21:46:27 +03:00
|
|
|
if (e.data) {
|
|
|
|
// e.objects.eventTarget will be defined if sending a response to
|
|
|
|
// a WebChannelMessageToChrome event. An unsolicited send
|
|
|
|
// may not have an eventTarget defined, in this case send to the
|
|
|
|
// main content window.
|
|
|
|
let eventTarget = e.objects.eventTarget || content;
|
|
|
|
|
|
|
|
// Use nodePrincipal if available, otherwise fallback to document principal.
|
|
|
|
let targetPrincipal = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget.document.nodePrincipal : eventTarget.nodePrincipal;
|
|
|
|
|
|
|
|
if (e.principal.subsumes(targetPrincipal)) {
|
|
|
|
// If eventTarget is a window, use it as the targetWindow, otherwise
|
|
|
|
// find the window that owns the eventTarget.
|
2017-01-27 12:51:03 +03:00
|
|
|
let targetWindow = eventTarget instanceof Ci.nsIDOMWindow ? eventTarget : eventTarget.ownerGlobal;
|
2015-06-30 21:46:27 +03:00
|
|
|
|
|
|
|
eventTarget.dispatchEvent(new targetWindow.CustomEvent("WebChannelMessageToContent", {
|
|
|
|
detail: Cu.cloneInto({
|
|
|
|
id: e.data.id,
|
|
|
|
message: e.data.message,
|
|
|
|
}, targetWindow),
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
Cu.reportError("WebChannel message failed. Principal mismatch.");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Cu.reportError("WebChannel message failed. No message data.");
|
|
|
|
}
|
|
|
|
});
|
2015-07-04 22:00:41 +03:00
|
|
|
|
2015-09-15 21:19:45 +03:00
|
|
|
var AudioPlaybackListener = {
|
2015-07-04 22:00:41 +03:00
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
|
|
|
|
|
|
|
init() {
|
2015-08-01 18:32:12 +03:00
|
|
|
Services.obs.addObserver(this, "audio-playback", false);
|
2017-03-28 07:47:02 +03:00
|
|
|
Services.obs.addObserver(this, "AudioFocusChanged", false);
|
|
|
|
Services.obs.addObserver(this, "MediaControl", false);
|
2016-04-18 13:48:41 +03:00
|
|
|
|
2016-05-03 04:51:22 +03:00
|
|
|
addMessageListener("AudioPlayback", this);
|
2015-07-04 22:00:41 +03:00
|
|
|
addEventListener("unload", () => {
|
2015-08-01 18:32:12 +03:00
|
|
|
AudioPlaybackListener.uninit();
|
2015-07-04 22:00:41 +03:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
uninit() {
|
2015-08-01 18:32:12 +03:00
|
|
|
Services.obs.removeObserver(this, "audio-playback");
|
2017-03-28 07:47:02 +03:00
|
|
|
Services.obs.removeObserver(this, "AudioFocusChanged");
|
|
|
|
Services.obs.removeObserver(this, "MediaControl");
|
2016-04-18 13:48:41 +03:00
|
|
|
|
2016-05-03 04:51:22 +03:00
|
|
|
removeMessageListener("AudioPlayback", this);
|
|
|
|
},
|
|
|
|
|
|
|
|
handleMediaControlMessage(msg) {
|
|
|
|
let utils = global.content.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIDOMWindowUtils);
|
2017-03-28 07:47:02 +03:00
|
|
|
let suspendTypes = Ci.nsISuspendedTypes;
|
2016-05-03 04:51:22 +03:00
|
|
|
switch (msg) {
|
|
|
|
case "mute":
|
|
|
|
utils.audioMuted = true;
|
|
|
|
break;
|
|
|
|
case "unmute":
|
|
|
|
utils.audioMuted = false;
|
|
|
|
break;
|
2017-03-28 07:47:02 +03:00
|
|
|
case "lostAudioFocus":
|
|
|
|
utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE_DISPOSABLE;
|
|
|
|
break;
|
|
|
|
case "lostAudioFocusTransiently":
|
|
|
|
utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE;
|
|
|
|
break;
|
|
|
|
case "gainAudioFocus":
|
|
|
|
utils.mediaSuspend = suspendTypes.NONE_SUSPENDED;
|
|
|
|
break;
|
|
|
|
case "mediaControlPaused":
|
|
|
|
utils.mediaSuspend = suspendTypes.SUSPENDED_PAUSE_DISPOSABLE;
|
|
|
|
break;
|
|
|
|
case "mediaControlStopped":
|
|
|
|
utils.mediaSuspend = suspendTypes.SUSPENDED_STOP_DISPOSABLE;
|
|
|
|
break;
|
|
|
|
case "blockInactivePageMedia":
|
|
|
|
utils.mediaSuspend = suspendTypes.SUSPENDED_BLOCK;
|
|
|
|
break;
|
|
|
|
case "resumeMedia":
|
|
|
|
utils.mediaSuspend = suspendTypes.NONE_SUSPENDED;
|
|
|
|
break;
|
2016-05-03 04:51:22 +03:00
|
|
|
default:
|
|
|
|
dump("Error : wrong media control msg!\n");
|
|
|
|
break;
|
|
|
|
}
|
2015-07-04 22:00:41 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
observe(subject, topic, data) {
|
2015-08-01 18:32:12 +03:00
|
|
|
if (topic === "audio-playback") {
|
2015-07-04 22:00:41 +03:00
|
|
|
if (subject && subject.top == global.content) {
|
2015-08-01 18:32:12 +03:00
|
|
|
let name = "AudioPlayback:";
|
2017-02-03 09:47:08 +03:00
|
|
|
if (data === "blockStart") {
|
|
|
|
name += "BlockStart";
|
|
|
|
} else if (data === "blockStop") {
|
|
|
|
name += "BlockStop";
|
2016-11-11 05:42:35 +03:00
|
|
|
} else {
|
|
|
|
name += (data === "active") ? "Start" : "Stop";
|
|
|
|
}
|
2015-07-04 22:00:41 +03:00
|
|
|
sendAsyncMessage(name);
|
|
|
|
}
|
2017-03-28 07:47:02 +03:00
|
|
|
} else if (topic == "AudioFocusChanged" || topic == "MediaControl") {
|
|
|
|
this.handleMediaControlMessage(data);
|
2015-07-04 22:00:41 +03:00
|
|
|
}
|
|
|
|
},
|
2015-07-05 03:47:14 +03:00
|
|
|
|
|
|
|
receiveMessage(msg) {
|
2016-05-03 04:51:22 +03:00
|
|
|
if (msg.name == "AudioPlayback") {
|
|
|
|
this.handleMediaControlMessage(msg.data.type);
|
2015-07-05 03:47:14 +03:00
|
|
|
}
|
|
|
|
},
|
2015-07-04 22:00:41 +03:00
|
|
|
};
|
2015-08-01 18:32:12 +03:00
|
|
|
AudioPlaybackListener.init();
|
2015-08-10 16:42:51 +03:00
|
|
|
|
2015-08-10 22:15:11 +03:00
|
|
|
addMessageListener("Browser:PurgeSessionHistory", function BrowserPurgeHistory() {
|
|
|
|
let sessionHistory = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
|
|
|
|
if (!sessionHistory) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// place the entry at current index at the end of the history list, so it won't get removed
|
|
|
|
if (sessionHistory.index < sessionHistory.count - 1) {
|
|
|
|
let indexEntry = sessionHistory.getEntryAtIndex(sessionHistory.index, false);
|
|
|
|
sessionHistory.QueryInterface(Components.interfaces.nsISHistoryInternal);
|
|
|
|
indexEntry.QueryInterface(Components.interfaces.nsISHEntry);
|
|
|
|
sessionHistory.addEntry(indexEntry, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
let purge = sessionHistory.count;
|
|
|
|
if (global.content.location.href != "about:blank") {
|
|
|
|
--purge; // Don't remove the page the user's staring at from shistory
|
|
|
|
}
|
|
|
|
|
|
|
|
if (purge > 0) {
|
|
|
|
sessionHistory.PurgeHistory(purge);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2015-09-15 21:19:45 +03:00
|
|
|
var ViewSelectionSource = {
|
2016-12-30 02:34:54 +03:00
|
|
|
init() {
|
2015-08-10 16:42:51 +03:00
|
|
|
addMessageListener("ViewSource:GetSelection", this);
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
receiveMessage(message) {
|
2015-08-10 16:42:51 +03:00
|
|
|
if (message.name == "ViewSource:GetSelection") {
|
|
|
|
let selectionDetails;
|
|
|
|
try {
|
|
|
|
selectionDetails = message.objects.target ? this.getMathMLSelection(message.objects.target)
|
|
|
|
: this.getSelection();
|
|
|
|
} finally {
|
|
|
|
sendAsyncMessage("ViewSource:GetSelectionDone", selectionDetails);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A helper to get a path like FIXptr, but with an array instead of the
|
|
|
|
* "tumbler" notation.
|
|
|
|
* See FIXptr: http://lists.w3.org/Archives/Public/www-xml-linking-comments/2001AprJun/att-0074/01-NOTE-FIXptr-20010425.htm
|
|
|
|
*/
|
2016-12-30 02:34:54 +03:00
|
|
|
getPath(ancestor, node) {
|
2015-08-10 16:42:51 +03:00
|
|
|
var n = node;
|
|
|
|
var p = n.parentNode;
|
|
|
|
if (n == ancestor || !p)
|
|
|
|
return null;
|
|
|
|
var path = new Array();
|
|
|
|
if (!path)
|
|
|
|
return null;
|
|
|
|
do {
|
|
|
|
for (var i = 0; i < p.childNodes.length; i++) {
|
|
|
|
if (p.childNodes.item(i) == n) {
|
|
|
|
path.push(i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
n = p;
|
|
|
|
p = n.parentNode;
|
|
|
|
} while (n != ancestor && p);
|
|
|
|
return path;
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
getSelection() {
|
2015-08-10 16:42:51 +03:00
|
|
|
// These are markers used to delimit the selection during processing. They
|
|
|
|
// are removed from the final rendering.
|
|
|
|
// We use noncharacter Unicode codepoints to minimize the risk of clashing
|
|
|
|
// with anything that might legitimately be present in the document.
|
|
|
|
// U+FDD0..FDEF <noncharacters>
|
|
|
|
const MARK_SELECTION_START = "\uFDD0";
|
|
|
|
const MARK_SELECTION_END = "\uFDEF";
|
|
|
|
|
|
|
|
var focusedWindow = Services.focus.focusedWindow || content;
|
|
|
|
var selection = focusedWindow.getSelection();
|
|
|
|
|
|
|
|
var range = selection.getRangeAt(0);
|
|
|
|
var ancestorContainer = range.commonAncestorContainer;
|
|
|
|
var doc = ancestorContainer.ownerDocument;
|
|
|
|
|
|
|
|
var startContainer = range.startContainer;
|
|
|
|
var endContainer = range.endContainer;
|
|
|
|
var startOffset = range.startOffset;
|
|
|
|
var endOffset = range.endOffset;
|
|
|
|
|
|
|
|
// let the ancestor be an element
|
|
|
|
var Node = doc.defaultView.Node;
|
|
|
|
if (ancestorContainer.nodeType == Node.TEXT_NODE ||
|
|
|
|
ancestorContainer.nodeType == Node.CDATA_SECTION_NODE)
|
|
|
|
ancestorContainer = ancestorContainer.parentNode;
|
|
|
|
|
|
|
|
// for selectAll, let's use the entire document, including <html>...</html>
|
|
|
|
// @see nsDocumentViewer::SelectAll() for how selectAll is implemented
|
|
|
|
try {
|
|
|
|
if (ancestorContainer == doc.body)
|
|
|
|
ancestorContainer = doc.documentElement;
|
|
|
|
} catch (e) { }
|
|
|
|
|
|
|
|
// each path is a "child sequence" (a.k.a. "tumbler") that
|
|
|
|
// descends from the ancestor down to the boundary point
|
|
|
|
var startPath = this.getPath(ancestorContainer, startContainer);
|
|
|
|
var endPath = this.getPath(ancestorContainer, endContainer);
|
|
|
|
|
|
|
|
// clone the fragment of interest and reset everything to be relative to it
|
|
|
|
// note: it is with the clone that we operate/munge from now on. Also note
|
|
|
|
// that we clone into a data document to prevent images in the fragment from
|
|
|
|
// loading and the like. The use of importNode here, as opposed to adoptNode,
|
|
|
|
// is _very_ important.
|
|
|
|
// XXXbz wish there were a less hacky way to create an untrusted document here
|
|
|
|
var isHTML = (doc.createElement("div").tagName == "DIV");
|
|
|
|
var dataDoc = isHTML ?
|
|
|
|
ancestorContainer.ownerDocument.implementation.createHTMLDocument("") :
|
|
|
|
ancestorContainer.ownerDocument.implementation.createDocument("", "", null);
|
|
|
|
ancestorContainer = dataDoc.importNode(ancestorContainer, true);
|
|
|
|
startContainer = ancestorContainer;
|
|
|
|
endContainer = ancestorContainer;
|
|
|
|
|
|
|
|
// Only bother with the selection if it can be remapped. Don't mess with
|
|
|
|
// leaf elements (such as <isindex>) that secretly use anynomous content
|
|
|
|
// for their display appearance.
|
|
|
|
var canDrawSelection = ancestorContainer.hasChildNodes();
|
|
|
|
var tmpNode;
|
|
|
|
if (canDrawSelection) {
|
|
|
|
var i;
|
2016-11-11 01:48:04 +03:00
|
|
|
for (i = startPath ? startPath.length - 1 : -1; i >= 0; i--) {
|
2015-08-10 16:42:51 +03:00
|
|
|
startContainer = startContainer.childNodes.item(startPath[i]);
|
|
|
|
}
|
2016-11-11 01:48:04 +03:00
|
|
|
for (i = endPath ? endPath.length - 1 : -1; i >= 0; i--) {
|
2015-08-10 16:42:51 +03:00
|
|
|
endContainer = endContainer.childNodes.item(endPath[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// add special markers to record the extent of the selection
|
|
|
|
// note: |startOffset| and |endOffset| are interpreted either as
|
|
|
|
// offsets in the text data or as child indices (see the Range spec)
|
|
|
|
// (here, munging the end point first to keep the start point safe...)
|
|
|
|
if (endContainer.nodeType == Node.TEXT_NODE ||
|
|
|
|
endContainer.nodeType == Node.CDATA_SECTION_NODE) {
|
|
|
|
// do some extra tweaks to try to avoid the view-source output to look like
|
|
|
|
// ...<tag>]... or ...]</tag>... (where ']' marks the end of the selection).
|
|
|
|
// To get a neat output, the idea here is to remap the end point from:
|
|
|
|
// 1. ...<tag>]... to ...]<tag>...
|
|
|
|
// 2. ...]</tag>... to ...</tag>]...
|
|
|
|
if ((endOffset > 0 && endOffset < endContainer.data.length) ||
|
|
|
|
!endContainer.parentNode || !endContainer.parentNode.parentNode)
|
|
|
|
endContainer.insertData(endOffset, MARK_SELECTION_END);
|
|
|
|
else {
|
|
|
|
tmpNode = dataDoc.createTextNode(MARK_SELECTION_END);
|
|
|
|
endContainer = endContainer.parentNode;
|
|
|
|
if (endOffset === 0)
|
|
|
|
endContainer.parentNode.insertBefore(tmpNode, endContainer);
|
|
|
|
else
|
|
|
|
endContainer.parentNode.insertBefore(tmpNode, endContainer.nextSibling);
|
|
|
|
}
|
2016-12-31 05:47:25 +03:00
|
|
|
} else {
|
2015-08-10 16:42:51 +03:00
|
|
|
tmpNode = dataDoc.createTextNode(MARK_SELECTION_END);
|
|
|
|
endContainer.insertBefore(tmpNode, endContainer.childNodes.item(endOffset));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (startContainer.nodeType == Node.TEXT_NODE ||
|
|
|
|
startContainer.nodeType == Node.CDATA_SECTION_NODE) {
|
|
|
|
// do some extra tweaks to try to avoid the view-source output to look like
|
|
|
|
// ...<tag>[... or ...[</tag>... (where '[' marks the start of the selection).
|
|
|
|
// To get a neat output, the idea here is to remap the start point from:
|
|
|
|
// 1. ...<tag>[... to ...[<tag>...
|
|
|
|
// 2. ...[</tag>... to ...</tag>[...
|
|
|
|
if ((startOffset > 0 && startOffset < startContainer.data.length) ||
|
|
|
|
!startContainer.parentNode || !startContainer.parentNode.parentNode ||
|
|
|
|
startContainer != startContainer.parentNode.lastChild)
|
|
|
|
startContainer.insertData(startOffset, MARK_SELECTION_START);
|
|
|
|
else {
|
|
|
|
tmpNode = dataDoc.createTextNode(MARK_SELECTION_START);
|
|
|
|
startContainer = startContainer.parentNode;
|
|
|
|
if (startOffset === 0)
|
|
|
|
startContainer.parentNode.insertBefore(tmpNode, startContainer);
|
|
|
|
else
|
|
|
|
startContainer.parentNode.insertBefore(tmpNode, startContainer.nextSibling);
|
|
|
|
}
|
2016-12-31 05:47:25 +03:00
|
|
|
} else {
|
2015-08-10 16:42:51 +03:00
|
|
|
tmpNode = dataDoc.createTextNode(MARK_SELECTION_START);
|
|
|
|
startContainer.insertBefore(tmpNode, startContainer.childNodes.item(startOffset));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// now extract and display the syntax highlighted source
|
|
|
|
tmpNode = dataDoc.createElementNS("http://www.w3.org/1999/xhtml", "div");
|
|
|
|
tmpNode.appendChild(ancestorContainer);
|
|
|
|
|
|
|
|
return { uri: (isHTML ? "view-source:data:text/html;charset=utf-8," :
|
|
|
|
"view-source:data:application/xml;charset=utf-8,")
|
|
|
|
+ encodeURIComponent(tmpNode.innerHTML),
|
|
|
|
drawSelection: canDrawSelection,
|
|
|
|
baseURI: doc.baseURI };
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reformat the source of a MathML node to highlight the node that was targetted.
|
|
|
|
*
|
|
|
|
* @param node
|
|
|
|
* Some element within the fragment of interest.
|
|
|
|
*/
|
2016-12-30 02:34:54 +03:00
|
|
|
getMathMLSelection(node) {
|
2017-01-27 12:51:03 +03:00
|
|
|
var Node = node.ownerGlobal.Node;
|
2015-08-10 16:42:51 +03:00
|
|
|
this._lineCount = 0;
|
|
|
|
this._startTargetLine = 0;
|
|
|
|
this._endTargetLine = 0;
|
|
|
|
this._targetNode = node;
|
|
|
|
if (this._targetNode && this._targetNode.nodeType == Node.TEXT_NODE)
|
|
|
|
this._targetNode = this._targetNode.parentNode;
|
|
|
|
|
|
|
|
// walk up the tree to the top-level element (e.g., <math>, <svg>)
|
|
|
|
var topTag = "math";
|
|
|
|
var topNode = this._targetNode;
|
|
|
|
while (topNode && topNode.localName != topTag) {
|
|
|
|
topNode = topNode.parentNode;
|
|
|
|
}
|
|
|
|
if (!topNode)
|
2016-02-04 00:12:18 +03:00
|
|
|
return undefined;
|
2015-08-10 16:42:51 +03:00
|
|
|
|
|
|
|
// serialize
|
|
|
|
const VIEW_SOURCE_CSS = "resource://gre-resources/viewsource.css";
|
|
|
|
const BUNDLE_URL = "chrome://global/locale/viewSource.properties";
|
|
|
|
|
|
|
|
let bundle = Services.strings.createBundle(BUNDLE_URL);
|
|
|
|
var title = bundle.GetStringFromName("viewMathMLSourceTitle");
|
2017-01-17 18:48:17 +03:00
|
|
|
var wrapClass = this.wrapLongLines ? ' class="wrap"' : "";
|
2015-08-10 16:42:51 +03:00
|
|
|
var source =
|
2017-01-17 18:48:17 +03:00
|
|
|
"<!DOCTYPE html>"
|
|
|
|
+ "<html>"
|
|
|
|
+ "<head><title>" + title + "</title>"
|
2015-08-10 16:42:51 +03:00
|
|
|
+ '<link rel="stylesheet" type="text/css" href="' + VIEW_SOURCE_CSS + '">'
|
|
|
|
+ '<style type="text/css">'
|
2017-01-17 18:48:17 +03:00
|
|
|
+ "#target { border: dashed 1px; background-color: lightyellow; }"
|
|
|
|
+ "</style>"
|
|
|
|
+ "</head>"
|
2015-08-10 16:42:51 +03:00
|
|
|
+ '<body id="viewsource"' + wrapClass
|
2016-12-31 05:47:25 +03:00
|
|
|
+ ' onload="document.title=\'' + title + '\'; document.getElementById(\'target\').scrollIntoView(true)">'
|
2017-01-17 18:48:17 +03:00
|
|
|
+ "<pre>"
|
2015-08-10 16:42:51 +03:00
|
|
|
+ this.getOuterMarkup(topNode, 0)
|
2017-01-17 18:48:17 +03:00
|
|
|
+ "</pre></body></html>"
|
2015-08-10 16:42:51 +03:00
|
|
|
; // end
|
|
|
|
|
|
|
|
return { uri: "data:text/html;charset=utf-8," + encodeURIComponent(source),
|
|
|
|
drawSelection: false, baseURI: node.ownerDocument.baseURI };
|
|
|
|
},
|
|
|
|
|
|
|
|
get wrapLongLines() {
|
|
|
|
return Services.prefs.getBoolPref("view_source.wrap_long_lines");
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
getInnerMarkup(node, indent) {
|
2017-01-17 18:48:17 +03:00
|
|
|
var str = "";
|
2015-08-10 16:42:51 +03:00
|
|
|
for (var i = 0; i < node.childNodes.length; i++) {
|
|
|
|
str += this.getOuterMarkup(node.childNodes.item(i), indent);
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
getOuterMarkup(node, indent) {
|
2017-01-27 12:51:03 +03:00
|
|
|
var Node = node.ownerGlobal.Node;
|
2015-08-10 16:42:51 +03:00
|
|
|
var newline = "";
|
|
|
|
var padding = "";
|
|
|
|
var str = "";
|
|
|
|
if (node == this._targetNode) {
|
|
|
|
this._startTargetLine = this._lineCount;
|
|
|
|
str += '</pre><pre id="target">';
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (node.nodeType) {
|
|
|
|
case Node.ELEMENT_NODE: // Element
|
|
|
|
// to avoid the wide gap problem, '\n' is not emitted on the first
|
|
|
|
// line and the lines before & after the <pre id="target">...</pre>
|
|
|
|
if (this._lineCount > 0 &&
|
|
|
|
this._lineCount != this._startTargetLine &&
|
|
|
|
this._lineCount != this._endTargetLine) {
|
|
|
|
newline = "\n";
|
|
|
|
}
|
|
|
|
this._lineCount++;
|
|
|
|
for (var k = 0; k < indent; k++) {
|
|
|
|
padding += " ";
|
|
|
|
}
|
|
|
|
str += newline + padding
|
2017-01-17 18:48:17 +03:00
|
|
|
+ '<<span class="start-tag">' + node.nodeName + "</span>";
|
2015-08-10 16:42:51 +03:00
|
|
|
for (var i = 0; i < node.attributes.length; i++) {
|
|
|
|
var attr = node.attributes.item(i);
|
|
|
|
if (attr.nodeName.match(/^[-_]moz/)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
str += ' <span class="attribute-name">'
|
2016-12-31 05:47:25 +03:00
|
|
|
+ attr.nodeName
|
|
|
|
+ '</span>=<span class="attribute-value">"'
|
|
|
|
+ this.unicodeToEntity(attr.nodeValue)
|
|
|
|
+ '"</span>';
|
2015-08-10 16:42:51 +03:00
|
|
|
}
|
|
|
|
if (!node.hasChildNodes()) {
|
|
|
|
str += "/>";
|
2016-12-31 05:47:25 +03:00
|
|
|
} else {
|
2015-08-10 16:42:51 +03:00
|
|
|
str += ">";
|
|
|
|
var oldLine = this._lineCount;
|
|
|
|
str += this.getInnerMarkup(node, indent + 2);
|
|
|
|
if (oldLine == this._lineCount) {
|
|
|
|
newline = "";
|
|
|
|
padding = "";
|
2016-12-31 05:47:25 +03:00
|
|
|
} else {
|
2015-08-10 16:42:51 +03:00
|
|
|
newline = (this._lineCount == this._endTargetLine) ? "" : "\n";
|
|
|
|
this._lineCount++;
|
|
|
|
}
|
|
|
|
str += newline + padding
|
2017-01-17 18:48:17 +03:00
|
|
|
+ '</<span class="end-tag">' + node.nodeName + "</span>>";
|
2015-08-10 16:42:51 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Node.TEXT_NODE: // Text
|
|
|
|
var tmp = node.nodeValue;
|
|
|
|
tmp = tmp.replace(/(\n|\r|\t)+/g, " ");
|
|
|
|
tmp = tmp.replace(/^ +/, "");
|
|
|
|
tmp = tmp.replace(/ +$/, "");
|
|
|
|
if (tmp.length != 0) {
|
2017-01-17 18:48:17 +03:00
|
|
|
str += '<span class="text">' + this.unicodeToEntity(tmp) + "</span>";
|
2015-08-10 16:42:51 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node == this._targetNode) {
|
|
|
|
this._endTargetLine = this._lineCount;
|
2017-01-17 18:48:17 +03:00
|
|
|
str += "</pre><pre>";
|
2015-08-10 16:42:51 +03:00
|
|
|
}
|
|
|
|
return str;
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
unicodeToEntity(text) {
|
2015-08-10 16:42:51 +03:00
|
|
|
const charTable = {
|
2017-01-17 18:48:17 +03:00
|
|
|
"&": '&<span class="entity">amp;</span>',
|
|
|
|
"<": '&<span class="entity">lt;</span>',
|
|
|
|
">": '&<span class="entity">gt;</span>',
|
2015-08-10 16:42:51 +03:00
|
|
|
'"': '&<span class="entity">quot;</span>'
|
|
|
|
};
|
|
|
|
|
|
|
|
function charTableLookup(letter) {
|
|
|
|
return charTable[letter];
|
|
|
|
}
|
|
|
|
|
|
|
|
function convertEntity(letter) {
|
|
|
|
try {
|
|
|
|
var unichar = this._entityConverter
|
|
|
|
.ConvertToEntity(letter, entityVersion);
|
|
|
|
var entity = unichar.substring(1); // extract '&'
|
2017-01-17 18:48:17 +03:00
|
|
|
return '&<span class="entity">' + entity + "</span>";
|
2015-08-10 16:42:51 +03:00
|
|
|
} catch (ex) {
|
|
|
|
return letter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this._entityConverter) {
|
|
|
|
try {
|
|
|
|
this._entityConverter = Cc["@mozilla.org/intl/entityconverter;1"]
|
|
|
|
.createInstance(Ci.nsIEntityConverter);
|
2016-08-04 10:28:58 +03:00
|
|
|
} catch (e) { }
|
2015-08-10 16:42:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
const entityVersion = Ci.nsIEntityConverter.entityW3C;
|
|
|
|
|
|
|
|
var str = text;
|
|
|
|
|
|
|
|
// replace chars in our charTable
|
|
|
|
str = str.replace(/[<>&"]/g, charTableLookup);
|
|
|
|
|
|
|
|
// replace chars > 0x7f via nsIEntityConverter
|
|
|
|
str = str.replace(/[^\0-\u007f]/g, convertEntity);
|
|
|
|
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
ViewSelectionSource.init();
|
2016-03-21 13:22:16 +03:00
|
|
|
|
|
|
|
addEventListener("MozApplicationManifest", function(e) {
|
|
|
|
let doc = e.target;
|
|
|
|
let info = {
|
|
|
|
uri: doc.documentURI,
|
|
|
|
characterSet: doc.characterSet,
|
|
|
|
manifest: doc.documentElement.getAttribute("manifest"),
|
|
|
|
principal: doc.nodePrincipal,
|
|
|
|
};
|
|
|
|
sendAsyncMessage("MozApplicationManifest", info);
|
|
|
|
}, false);
|
|
|
|
|
2016-07-28 21:18:50 +03:00
|
|
|
let AutoCompletePopup = {
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompletePopup]),
|
|
|
|
|
|
|
|
_connected: false,
|
|
|
|
|
Bug 1296638 - AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm need to have popupOpen be in sync. r=MattN
The opening of the AutoCompletePopup will always start from content, but closing the
popup can occur in the parent (for example, if the user switches focus from the browser),
or in content (the user hits Esc, for example, which tells the parent to close the popup).
This relationship between content and the popup has been true for a while, but the
patch in bug 1294502 didn't account for it. In particular, before this patch, it was
possible for AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm to get
out of sync on whether or not the popup is open.
Mainly, this is because the parent wasn't telling the content that the popup had
hidden if the hide was initialized by the parent. The other reason, was because
the popupOpen state in browser-content.js was being set immediately, instead
of waiting for the parent to report that the popup had indeed opened or closed.
MozReview-Commit-ID: CRkg49lP1Hd
--HG--
extra : rebase_source : 0a0383170fc78f8639713a5879ab18468fc0eef7
2016-09-17 19:44:04 +03:00
|
|
|
MESSAGES: [
|
|
|
|
"FormAutoComplete:HandleEnter",
|
|
|
|
"FormAutoComplete:PopupClosed",
|
2016-08-20 00:15:56 +03:00
|
|
|
"FormAutoComplete:PopupOpened",
|
|
|
|
"FormAutoComplete:RequestFocus",
|
Bug 1296638 - AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm need to have popupOpen be in sync. r=MattN
The opening of the AutoCompletePopup will always start from content, but closing the
popup can occur in the parent (for example, if the user switches focus from the browser),
or in content (the user hits Esc, for example, which tells the parent to close the popup).
This relationship between content and the popup has been true for a while, but the
patch in bug 1294502 didn't account for it. In particular, before this patch, it was
possible for AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm to get
out of sync on whether or not the popup is open.
Mainly, this is because the parent wasn't telling the content that the popup had
hidden if the hide was initialized by the parent. The other reason, was because
the popupOpen state in browser-content.js was being set immediately, instead
of waiting for the parent to report that the popup had indeed opened or closed.
MozReview-Commit-ID: CRkg49lP1Hd
--HG--
extra : rebase_source : 0a0383170fc78f8639713a5879ab18468fc0eef7
2016-09-17 19:44:04 +03:00
|
|
|
],
|
2016-07-28 21:18:50 +03:00
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
init() {
|
Bug 1296638 - AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm need to have popupOpen be in sync. r=MattN
The opening of the AutoCompletePopup will always start from content, but closing the
popup can occur in the parent (for example, if the user switches focus from the browser),
or in content (the user hits Esc, for example, which tells the parent to close the popup).
This relationship between content and the popup has been true for a while, but the
patch in bug 1294502 didn't account for it. In particular, before this patch, it was
possible for AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm to get
out of sync on whether or not the popup is open.
Mainly, this is because the parent wasn't telling the content that the popup had
hidden if the hide was initialized by the parent. The other reason, was because
the popupOpen state in browser-content.js was being set immediately, instead
of waiting for the parent to report that the popup had indeed opened or closed.
MozReview-Commit-ID: CRkg49lP1Hd
--HG--
extra : rebase_source : 0a0383170fc78f8639713a5879ab18468fc0eef7
2016-09-17 19:44:04 +03:00
|
|
|
addEventListener("unload", this);
|
|
|
|
addEventListener("DOMContentLoaded", this);
|
2016-07-28 21:18:50 +03:00
|
|
|
|
Bug 1296638 - AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm need to have popupOpen be in sync. r=MattN
The opening of the AutoCompletePopup will always start from content, but closing the
popup can occur in the parent (for example, if the user switches focus from the browser),
or in content (the user hits Esc, for example, which tells the parent to close the popup).
This relationship between content and the popup has been true for a while, but the
patch in bug 1294502 didn't account for it. In particular, before this patch, it was
possible for AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm to get
out of sync on whether or not the popup is open.
Mainly, this is because the parent wasn't telling the content that the popup had
hidden if the hide was initialized by the parent. The other reason, was because
the popupOpen state in browser-content.js was being set immediately, instead
of waiting for the parent to report that the popup had indeed opened or closed.
MozReview-Commit-ID: CRkg49lP1Hd
--HG--
extra : rebase_source : 0a0383170fc78f8639713a5879ab18468fc0eef7
2016-09-17 19:44:04 +03:00
|
|
|
for (let messageName of this.MESSAGES) {
|
|
|
|
addMessageListener(messageName, this);
|
|
|
|
}
|
2016-07-28 21:18:50 +03:00
|
|
|
|
Bug 1296638 - AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm need to have popupOpen be in sync. r=MattN
The opening of the AutoCompletePopup will always start from content, but closing the
popup can occur in the parent (for example, if the user switches focus from the browser),
or in content (the user hits Esc, for example, which tells the parent to close the popup).
This relationship between content and the popup has been true for a while, but the
patch in bug 1294502 didn't account for it. In particular, before this patch, it was
possible for AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm to get
out of sync on whether or not the popup is open.
Mainly, this is because the parent wasn't telling the content that the popup had
hidden if the hide was initialized by the parent. The other reason, was because
the popupOpen state in browser-content.js was being set immediately, instead
of waiting for the parent to report that the popup had indeed opened or closed.
MozReview-Commit-ID: CRkg49lP1Hd
--HG--
extra : rebase_source : 0a0383170fc78f8639713a5879ab18468fc0eef7
2016-09-17 19:44:04 +03:00
|
|
|
this._input = null;
|
|
|
|
this._popupOpen = false;
|
2016-07-28 21:18:50 +03:00
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
destroy() {
|
2016-07-28 21:18:50 +03:00
|
|
|
if (this._connected) {
|
|
|
|
let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
|
|
|
|
.getService(Ci.nsIFormFillController);
|
|
|
|
controller.detachFromBrowser(docShell);
|
|
|
|
this._connected = false;
|
|
|
|
}
|
Bug 1296638 - AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm need to have popupOpen be in sync. r=MattN
The opening of the AutoCompletePopup will always start from content, but closing the
popup can occur in the parent (for example, if the user switches focus from the browser),
or in content (the user hits Esc, for example, which tells the parent to close the popup).
This relationship between content and the popup has been true for a while, but the
patch in bug 1294502 didn't account for it. In particular, before this patch, it was
possible for AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm to get
out of sync on whether or not the popup is open.
Mainly, this is because the parent wasn't telling the content that the popup had
hidden if the hide was initialized by the parent. The other reason, was because
the popupOpen state in browser-content.js was being set immediately, instead
of waiting for the parent to report that the popup had indeed opened or closed.
MozReview-Commit-ID: CRkg49lP1Hd
--HG--
extra : rebase_source : 0a0383170fc78f8639713a5879ab18468fc0eef7
2016-09-17 19:44:04 +03:00
|
|
|
|
|
|
|
removeEventListener("unload", this);
|
|
|
|
removeEventListener("DOMContentLoaded", this);
|
|
|
|
|
|
|
|
for (let messageName of this.MESSAGES) {
|
|
|
|
removeMessageListener(messageName, this);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
handleEvent(event) {
|
|
|
|
switch (event.type) {
|
|
|
|
case "DOMContentLoaded": {
|
|
|
|
removeEventListener("DOMContentLoaded", this);
|
|
|
|
|
|
|
|
// We need to wait for a content viewer to be available
|
|
|
|
// before we can attach our AutoCompletePopup handler,
|
|
|
|
// since nsFormFillController assumes one will exist
|
|
|
|
// when we call attachToBrowser.
|
|
|
|
|
|
|
|
// Hook up the form fill autocomplete controller.
|
|
|
|
let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"]
|
|
|
|
.getService(Ci.nsIFormFillController);
|
|
|
|
controller.attachToBrowser(docShell,
|
|
|
|
this.QueryInterface(Ci.nsIAutoCompletePopup));
|
|
|
|
this._connected = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case "unload": {
|
|
|
|
this.destroy();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
receiveMessage(message) {
|
|
|
|
switch (message.name) {
|
|
|
|
case "FormAutoComplete:HandleEnter": {
|
|
|
|
this.selectedIndex = message.data.selectedIndex;
|
|
|
|
|
|
|
|
let controller = Cc["@mozilla.org/autocomplete/controller;1"]
|
|
|
|
.getService(Ci.nsIAutoCompleteController);
|
|
|
|
controller.handleEnter(message.data.isPopupSelection);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case "FormAutoComplete:PopupClosed": {
|
|
|
|
this._popupOpen = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case "FormAutoComplete:PopupOpened": {
|
|
|
|
this._popupOpen = true;
|
|
|
|
break;
|
|
|
|
}
|
2016-08-20 00:15:56 +03:00
|
|
|
|
|
|
|
case "FormAutoComplete:RequestFocus": {
|
|
|
|
if (this._input) {
|
|
|
|
this._input.focus();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
Bug 1296638 - AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm need to have popupOpen be in sync. r=MattN
The opening of the AutoCompletePopup will always start from content, but closing the
popup can occur in the parent (for example, if the user switches focus from the browser),
or in content (the user hits Esc, for example, which tells the parent to close the popup).
This relationship between content and the popup has been true for a while, but the
patch in bug 1294502 didn't account for it. In particular, before this patch, it was
possible for AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm to get
out of sync on whether or not the popup is open.
Mainly, this is because the parent wasn't telling the content that the popup had
hidden if the hide was initialized by the parent. The other reason, was because
the popupOpen state in browser-content.js was being set immediately, instead
of waiting for the parent to report that the popup had indeed opened or closed.
MozReview-Commit-ID: CRkg49lP1Hd
--HG--
extra : rebase_source : 0a0383170fc78f8639713a5879ab18468fc0eef7
2016-09-17 19:44:04 +03:00
|
|
|
}
|
2016-07-28 21:18:50 +03:00
|
|
|
},
|
|
|
|
|
2016-11-12 02:22:34 +03:00
|
|
|
get input() { return this._input; },
|
|
|
|
get overrideValue() { return null; },
|
|
|
|
set selectedIndex(index) {
|
2016-08-18 17:50:58 +03:00
|
|
|
sendAsyncMessage("FormAutoComplete:SetSelectedIndex", { index });
|
|
|
|
},
|
2016-11-12 02:22:34 +03:00
|
|
|
get selectedIndex() {
|
2016-07-28 21:18:50 +03:00
|
|
|
// selectedIndex getter must be synchronous because we need the
|
|
|
|
// correct value when the controller is in controller::HandleEnter.
|
|
|
|
// We can't easily just let the parent inform us the new value every
|
|
|
|
// time it changes because not every action that can change the
|
|
|
|
// selectedIndex is trivial to catch (e.g. moving the mouse over the
|
|
|
|
// list).
|
|
|
|
return sendSyncMessage("FormAutoComplete:GetSelectedIndex", {});
|
|
|
|
},
|
2016-11-12 02:22:34 +03:00
|
|
|
get popupOpen() {
|
2016-07-28 21:18:50 +03:00
|
|
|
return this._popupOpen;
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
openAutocompletePopup(input, element) {
|
2016-08-18 17:50:58 +03:00
|
|
|
if (this._popupOpen || !input) {
|
|
|
|
return;
|
2016-07-28 21:18:50 +03:00
|
|
|
}
|
2016-08-18 17:50:58 +03:00
|
|
|
|
|
|
|
let rect = BrowserUtils.getElementBoundingScreenRect(element);
|
2017-01-27 12:51:03 +03:00
|
|
|
let window = element.ownerGlobal;
|
2016-08-18 17:50:58 +03:00
|
|
|
let dir = window.getComputedStyle(element).direction;
|
|
|
|
let results = this.getResultsFromController(input);
|
|
|
|
|
|
|
|
sendAsyncMessage("FormAutoComplete:MaybeOpenPopup",
|
|
|
|
{ results, rect, dir });
|
2016-07-28 21:18:50 +03:00
|
|
|
this._input = input;
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
closePopup() {
|
Bug 1296638 - AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm need to have popupOpen be in sync. r=MattN
The opening of the AutoCompletePopup will always start from content, but closing the
popup can occur in the parent (for example, if the user switches focus from the browser),
or in content (the user hits Esc, for example, which tells the parent to close the popup).
This relationship between content and the popup has been true for a while, but the
patch in bug 1294502 didn't account for it. In particular, before this patch, it was
possible for AutoCompletePopup in browser-content.js and AutoCompletePopup.jsm to get
out of sync on whether or not the popup is open.
Mainly, this is because the parent wasn't telling the content that the popup had
hidden if the hide was initialized by the parent. The other reason, was because
the popupOpen state in browser-content.js was being set immediately, instead
of waiting for the parent to report that the popup had indeed opened or closed.
MozReview-Commit-ID: CRkg49lP1Hd
--HG--
extra : rebase_source : 0a0383170fc78f8639713a5879ab18468fc0eef7
2016-09-17 19:44:04 +03:00
|
|
|
// We set this here instead of just waiting for the
|
|
|
|
// PopupClosed message to do it so that we don't end
|
|
|
|
// up in a state where the content thinks that a popup
|
|
|
|
// is open when it isn't (or soon won't be).
|
2016-07-28 21:18:50 +03:00
|
|
|
this._popupOpen = false;
|
|
|
|
sendAsyncMessage("FormAutoComplete:ClosePopup", {});
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
invalidate() {
|
2016-08-18 17:50:58 +03:00
|
|
|
if (this._popupOpen) {
|
|
|
|
let results = this.getResultsFromController(this._input);
|
|
|
|
sendAsyncMessage("FormAutoComplete:Invalidate", { results });
|
|
|
|
}
|
2016-07-28 21:18:50 +03:00
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
selectBy(reverse, page) {
|
2016-07-28 21:18:50 +03:00
|
|
|
this._index = sendSyncMessage("FormAutoComplete:SelectBy", {
|
2016-12-30 02:34:54 +03:00
|
|
|
reverse,
|
|
|
|
page
|
2016-07-28 21:18:50 +03:00
|
|
|
});
|
2016-08-18 17:50:58 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
getResultsFromController(inputField) {
|
|
|
|
let results = [];
|
|
|
|
|
|
|
|
if (!inputField) {
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
let controller = inputField.controller;
|
|
|
|
if (!(controller instanceof Ci.nsIAutoCompleteController)) {
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < controller.matchCount; ++i) {
|
|
|
|
let result = {};
|
|
|
|
result.value = controller.getValueAt(i);
|
|
|
|
result.label = controller.getLabelAt(i);
|
|
|
|
result.comment = controller.getCommentAt(i);
|
|
|
|
result.style = controller.getStyleAt(i);
|
|
|
|
result.image = controller.getImageAt(i);
|
|
|
|
results.push(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
return results;
|
2016-08-20 00:15:56 +03:00
|
|
|
},
|
2016-07-28 21:18:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
AutoCompletePopup.init();
|
2016-10-06 07:17:00 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* DateTimePickerListener is the communication channel between the input box
|
|
|
|
* (content) for date/time input types and its picker (chrome).
|
|
|
|
*/
|
|
|
|
let DateTimePickerListener = {
|
|
|
|
/**
|
|
|
|
* On init, just listen for the event to open the picker, once the picker is
|
|
|
|
* opened, we'll listen for update and close events.
|
|
|
|
*/
|
2016-12-30 02:34:54 +03:00
|
|
|
init() {
|
2016-10-06 07:17:00 +03:00
|
|
|
addEventListener("MozOpenDateTimePicker", this);
|
|
|
|
this._inputElement = null;
|
|
|
|
|
|
|
|
addEventListener("unload", () => {
|
|
|
|
this.uninit();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
uninit() {
|
2016-10-06 07:17:00 +03:00
|
|
|
removeEventListener("MozOpenDateTimePicker", this);
|
|
|
|
this._inputElement = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cleanup function called when picker is closed.
|
|
|
|
*/
|
2016-12-30 02:34:54 +03:00
|
|
|
close() {
|
2016-10-06 07:17:00 +03:00
|
|
|
this.removeListeners();
|
|
|
|
this._inputElement.setDateTimePickerState(false);
|
|
|
|
this._inputElement = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called after picker is opened to start listening for input box update
|
|
|
|
* events.
|
|
|
|
*/
|
2016-12-30 02:34:54 +03:00
|
|
|
addListeners() {
|
2016-10-06 07:17:00 +03:00
|
|
|
addEventListener("MozUpdateDateTimePicker", this);
|
|
|
|
addEventListener("MozCloseDateTimePicker", this);
|
|
|
|
addEventListener("pagehide", this);
|
|
|
|
|
|
|
|
addMessageListener("FormDateTime:PickerValueChanged", this);
|
|
|
|
addMessageListener("FormDateTime:PickerClosed", this);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop listeneing for events when picker is closed.
|
|
|
|
*/
|
2016-12-30 02:34:54 +03:00
|
|
|
removeListeners() {
|
2016-10-06 07:17:00 +03:00
|
|
|
removeEventListener("MozUpdateDateTimePicker", this);
|
|
|
|
removeEventListener("MozCloseDateTimePicker", this);
|
|
|
|
removeEventListener("pagehide", this);
|
|
|
|
|
|
|
|
removeMessageListener("FormDateTime:PickerValueChanged", this);
|
|
|
|
removeMessageListener("FormDateTime:PickerClosed", this);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function that returns the CSS direction property of the element.
|
|
|
|
*/
|
2016-12-30 02:34:54 +03:00
|
|
|
getComputedDirection(aElement) {
|
2017-01-27 12:51:03 +03:00
|
|
|
return aElement.ownerGlobal.getComputedStyle(aElement)
|
2016-10-06 07:17:00 +03:00
|
|
|
.getPropertyValue("direction");
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function that returns the rect of the element, which is the position
|
2016-09-06 08:01:40 +03:00
|
|
|
* relative to the left/top of the content area.
|
2016-10-06 07:17:00 +03:00
|
|
|
*/
|
2016-12-30 02:34:54 +03:00
|
|
|
getBoundingContentRect(aElement) {
|
2016-09-06 08:01:40 +03:00
|
|
|
return BrowserUtils.getElementBoundingRect(aElement);
|
2016-11-08 14:07:50 +03:00
|
|
|
},
|
|
|
|
|
2016-12-30 02:34:54 +03:00
|
|
|
getTimePickerPref() {
|
2016-11-08 14:07:50 +03:00
|
|
|
return Services.prefs.getBoolPref("dom.forms.datetime.timepicker");
|
2016-10-06 07:17:00 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nsIMessageListener.
|
|
|
|
*/
|
2016-12-30 02:34:54 +03:00
|
|
|
receiveMessage(aMessage) {
|
2016-10-06 07:17:00 +03:00
|
|
|
switch (aMessage.name) {
|
|
|
|
case "FormDateTime:PickerClosed": {
|
|
|
|
this.close();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "FormDateTime:PickerValueChanged": {
|
|
|
|
this._inputElement.updateDateTimeInputBox(aMessage.data);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nsIDOMEventListener, for chrome events sent by the input element and other
|
|
|
|
* DOM events.
|
|
|
|
*/
|
2016-12-30 02:34:54 +03:00
|
|
|
handleEvent(aEvent) {
|
2016-10-06 07:17:00 +03:00
|
|
|
switch (aEvent.type) {
|
|
|
|
case "MozOpenDateTimePicker": {
|
2016-11-08 14:07:50 +03:00
|
|
|
// Time picker is disabled when preffed off
|
|
|
|
if (!(aEvent.originalTarget instanceof content.HTMLInputElement) ||
|
|
|
|
(aEvent.originalTarget.type == "time" && !this.getTimePickerPref())) {
|
2016-10-06 07:17:00 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._inputElement = aEvent.originalTarget;
|
|
|
|
this._inputElement.setDateTimePickerState(true);
|
|
|
|
this.addListeners();
|
|
|
|
|
|
|
|
let value = this._inputElement.getDateTimeInputBoxValue();
|
|
|
|
sendAsyncMessage("FormDateTime:OpenPicker", {
|
|
|
|
rect: this.getBoundingContentRect(this._inputElement),
|
|
|
|
dir: this.getComputedDirection(this._inputElement),
|
|
|
|
type: this._inputElement.type,
|
|
|
|
detail: {
|
|
|
|
// Pass partial value if it's available, otherwise pass input
|
|
|
|
// element's value.
|
|
|
|
value: Object.keys(value).length > 0 ? value
|
|
|
|
: this._inputElement.value,
|
|
|
|
step: this._inputElement.step,
|
|
|
|
min: this._inputElement.min,
|
|
|
|
max: this._inputElement.max,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "MozUpdateDateTimePicker": {
|
|
|
|
let value = this._inputElement.getDateTimeInputBoxValue();
|
2017-01-17 08:09:03 +03:00
|
|
|
value.type = this._inputElement.type;
|
2016-10-06 07:17:00 +03:00
|
|
|
sendAsyncMessage("FormDateTime:UpdatePicker", { value });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "MozCloseDateTimePicker": {
|
|
|
|
sendAsyncMessage("FormDateTime:ClosePicker");
|
|
|
|
this.close();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "pagehide": {
|
|
|
|
if (this._inputElement &&
|
|
|
|
this._inputElement.ownerDocument == aEvent.target) {
|
|
|
|
sendAsyncMessage("FormDateTime:ClosePicker");
|
|
|
|
this.close();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
DateTimePickerListener.init();
|
2016-12-25 03:15:24 +03:00
|
|
|
|
2016-12-21 19:09:39 +03:00
|
|
|
addEventListener("mozshowdropdown", event => {
|
|
|
|
if (!event.isTrusted)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!SelectContentHelper.open) {
|
|
|
|
new SelectContentHelper(event.target, {isOpenedViaTouch: false}, this);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
addEventListener("mozshowdropdown-sourcetouch", event => {
|
|
|
|
if (!event.isTrusted)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!SelectContentHelper.open) {
|
|
|
|
new SelectContentHelper(event.target, {isOpenedViaTouch: true}, this);
|
|
|
|
}
|
|
|
|
});
|