You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

492 lines
11 KiB

/* 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/. */
"use strict";
const EXPORTED_SYMBOLS = ["S4EStatusService"];
const CU = Components.utils;
CU.import("resource://gre/modules/Services.jsm");
CU.import("resource://gre/modules/XPCOMUtils.jsm");
function S4EStatusService(window, service, getters)
{
this._window = window;
this._service = service;
this._getters = getters;
this._overLinkService = new S4EOverlinkService(this._window, this._service, this);
}
S4EStatusService.prototype =
{
_window: null,
_service: null,
_getters: null,
_overLinkService: null,
_overLink: { val: "", type: "" },
_network: { val: "", type: "" },
_networkXHR: { val: "", type: "" },
_status: { val: "", type: "" },
_jsStatus: { val: "", type: "" },
_defaultStatus: { val: "", type: "" },
_isFullScreen: false,
_isVideo: false,
_statusText: { val: "", type: "" },
_noUpdate: false,
_statusChromeTimeoutID: 0,
_statusContentTimeoutID: 0,
getCompositeStatusText: function()
{
return this._statusText.val;
},
getStatusText: function()
{
return this._status.val;
},
setNetworkStatus: function(status, busy)
{
if(busy)
{
this._network = { val: status, type: "network" };
this._networkXHR = { val: "", type: "network xhr" };
}
else
{
this._networkXHR = { val: status, type: "network xhr" };
}
this.updateStatusField();
},
setStatusText: function(status)
{
this._status = { val: status, type: "status chrome" };
this.updateStatusField();
},
setJSStatus: function(status)
{
this._jsStatus = { val: status, type: "status content" };
this.updateStatusField();
},
setJSDefaultStatus: function(status)
{
// This was removed from Firefox in Bug 862917
},
setDefaultStatus: function(status)
{
this._defaultStatus = { val: status, type: "status chrome default" };
this.updateStatusField();
},
setOverLink: function(link, aAnchor)
{
this._overLinkService.update(link, aAnchor);
},
setOverLinkInternal: function(link, aAnchor)
{
let status = this._service.status;
let statusLinkOver = this._service.statusLinkOver;
if(statusLinkOver)
{
link = link.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g, encodeURIComponent);
if(status == statusLinkOver)
{
this._overLink = { val: link, type: "overLink", anchor: aAnchor };
this.updateStatusField();
}
else
{
this.setStatusField(statusLinkOver, { val: link, type: "overLink", anchor: aAnchor }, true);
}
}
},
setNoUpdate: function(nu)
{
this._noUpdate = nu;
},
buildBinding: function() {
// Object.prototype.watch() shim, based on Eli Grey's polyfill
// object.watch
if (!this._window.XULBrowserWindow.watch) {
Object.defineProperty(this._window.XULBrowserWindow, "watch", {
enumerable: false,
configurable: true,
writable: false,
value: function (prop, handler) {
var oldval = this[prop],
newval = oldval,
getter = function () {
return newval;
},
setter = function (val) {
oldval = newval;
return newval = handler.call(this, prop, oldval, val);
}
;
try {
if (delete this[prop]) { // can't watch constants
Object.defineProperty(this, prop, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
} catch(e) {
// This fails fatally on non-configurable props, so just
// ignore errors if it does.
}
}
});
}
// object.unwatch
if (!this._window.XULBrowserWindow.unwatch) {
Object.defineProperty(this._window.XULBrowserWindow, "unwatch", {
enumerable: false,
configurable: true,
writable: false,
value: function (prop) {
var val = this[prop];
delete this[prop]; // remove accessors
this[prop] = val;
}
});
}
let XULBWPropHandler = function(prop, oldval, newval) {
CU.reportError("Attempt to modify XULBrowserWindow." + prop);
return oldval;
};
["updateStatusField", "onStatusChange"].forEach(function(prop)
{
this._window.XULBrowserWindow.unwatch(prop);
this._window.XULBrowserWindow[prop] = function() {};
this._window.XULBrowserWindow.watch(prop, XULBWPropHandler);
}, this);
["getCompositeStatusText", "getStatusText", "setStatusText", "setJSStatus",
"setJSDefaultStatus", "setDefaultStatus", "setOverLink"].forEach(function(prop)
{
this._window.XULBrowserWindow.unwatch(prop);
this._window.XULBrowserWindow[prop] = this[prop].bind(this);
this._window.XULBrowserWindow.watch(prop, XULBWPropHandler);
}, this);
},
destroy: function()
{
// No need to unbind from the XULBrowserWindow, it's already null at this point
this.clearTimer("_statusChromeTimeoutID");
this.clearTimer("_statusContentTimeoutID");
this._overLinkService.destroy();
["_overLink", "_network", "_networkXHR", "_status", "_jsStatus", "_defaultStatus",
"_statusText", "_window", "_service", "_getters", "_overLinkService"].forEach(function(prop)
{
delete this[prop];
}, this);
},
buildTextOrder: function()
{
this.__defineGetter__("_textOrder", function()
{
let textOrder = ["_overLink"];
if(this._service.statusNetwork)
{
textOrder.push("_network");
if(this._service.statusNetworkXHR)
{
textOrder.push("_networkXHR");
}
}
textOrder.push("_status", "_jsStatus");
if(this._service.statusDefault)
{
textOrder.push("_defaultStatus");
}
delete this._textOrder;
return this._textOrder = textOrder;
});
},
updateStatusField: function(force)
{
let text = { val: "", type: "" };
for(let i = 0; !text.val && i < this._textOrder.length; i++)
{
text = this[this._textOrder[i]];
}
if(this._statusText.val != text.val || force)
{
if(this._noUpdate)
{
return;
}
this._statusText = text;
this.setStatusField(this._service.status, text, false);
if(text.val && this._service.statusTimeout)
{
this.setTimer(text.type);
}
}
},
setFullScreenState: function(isFullScreen, isVideo)
{
this._isFullScreen = isFullScreen;
this._isVideo = isFullScreen && isVideo;
this.clearStatusField();
this.updateStatusField(true);
},
setTimer: function(type)
{
let typeArgs = type.split(" ", 3);
if(typeArgs.length < 2 || typeArgs[0] != "status")
{
return;
}
if(typeArgs[1] == "chrome")
{
this.clearTimer("_statusChromeTimeoutID");
this._statusChromeTimeoutID = this._window.setTimeout(function(self, isDefault)
{
self._statusChromeTimeoutID = 0;
if(isDefault)
{
self.setDefaultStatus("");
}
else
{
self.setStatusText("");
}
}, this._service.statusTimeout, this, (typeArgs.length == 3 && typeArgs[2] == "default"));
}
else
{
this.clearTimer("_statusContentTimeoutID");
this._statusContentTimeoutID = this._window.setTimeout(function(self)
{
self._statusContentTimeoutID = 0;
self.setJSStatus("");
}, this._service.statusTimeout, this);
}
},
clearTimer: function(timerName)
{
if(this[timerName] != 0)
{
this._window.clearTimeout(this[timerName]);
this[timerName] = 0;
}
},
clearStatusField: function()
{
this._getters.statusOverlay.value = "";
let status_label = this._getters.statusWidgetLabel;
if(status_label)
{
status_label.value = "";
}
},
setStatusField: function(location, text, allowTooltip)
{
if(!location)
{
return;
}
let label = null;
if(this._isFullScreen)
{
switch(location)
{
case 1: // Toolbar
location = 3
break;
case 2: // URL bar
if(Services.prefs.getBoolPref("browser.fullscreen.autohide"))
{
location = 3
}
break;
}
}
switch(location)
{
case 1: // Toolbar
label = this._getters.statusWidgetLabel;
break;
case 2: // URL Bar
break;
case 3: // Popup
default:
if(this._isVideo)
{
return;
}
label = this._getters.statusOverlay;
break;
}
if(label)
{
label.setAttribute("previoustype", label.getAttribute("type"));
label.setAttribute("type", text.type);
label.value = text.val;
label.setAttribute("crop", text.type == "overLink" ? "center" : "end");
}
}
};
function S4EOverlinkService(window, service, statusService) {
this._window = window;
this._service = service;
this._statusService = statusService;
}
S4EOverlinkService.prototype =
{
_window: null,
_service: null,
_statusService: null,
_timer: 0,
_currentLink: { link: "", anchor: null },
_pendingLink: { link: "", anchor: null },
_listening: false,
update: function(aLink, aAnchor)
{
this.clearTimer();
this.stopListen();
this._pendingLink = { link: aLink, anchor: aAnchor };
if(!aLink)
{
if(this._window.XULBrowserWindow.hideOverLinkImmediately || !this._service.statusLinkOverDelayHide)
{
this._show();
}
else
{
this._showDelayed();
}
}
else if(this._currentLink.link || !this._service.statusLinkOverDelayShow)
{
this._show();
}
else
{
this._showDelayed();
this.startListen();
}
},
destroy: function()
{
this.clearTimer();
this.stopListen();
["_currentLink", "_pendingLink", "_statusService", "_window"].forEach(function(prop)
{
delete this[prop];
}, this);
},
startListen: function()
{
if(!this._listening)
{
this._window.addEventListener("mousemove", this, true);
this._listening = true;
}
},
stopListen: function()
{
if(this._listening)
{
this._window.removeEventListener("mousemove", this, true);
this._listening = false;
}
},
clearTimer: function()
{
if(this._timer != 0)
{
this._window.clearTimeout(this._timer);
this._timer = 0;
}
},
handleEvent: function(event)
{
switch(event.type)
{
case "mousemove":
this.clearTimer();
this._showDelayed();
}
},
_showDelayed: function()
{
let delay = ((this._pendingLink.link)
? this._service.statusLinkOverDelayShow
: this._service.statusLinkOverDelayHide);
this._timer = this._window.setTimeout(function(self)
{
self._timer = 0;
self._show();
self.stopListen();
}, delay, this);
},
_show: function()
{
this._currentLink = this._pendingLink;
this._statusService.setOverLinkInternal(this._currentLink.link, this._currentLink.anchor);
}
};