This commit is contained in:
Benjamin Stover 2010-09-08 10:51:29 -07:00
Родитель ca20157ecf 0c3e14f841
Коммит 13adc3d3fb
15 изменённых файлов: 137 добавлений и 133 удалений

Просмотреть файл

@ -46,7 +46,7 @@
const kDoubleClickInterval = 400; const kDoubleClickInterval = 400;
// threshold in ms to detect if the click is possibly a dblClick // threshold in ms to detect if the click is possibly a dblClick
const kDoubleClickThreshold = 200; const kDoubleClickThreshold = 300;
// threshold in pixels for sensing a tap as opposed to a pan // threshold in pixels for sensing a tap as opposed to a pan
const kTapRadius = 25; const kTapRadius = 25;
@ -339,8 +339,8 @@ function MouseModule(owner, browserViewContainer) {
this._targetScrollInterface = null; this._targetScrollInterface = null;
var self = this; var self = this;
this._kinetic = new KineticController(Util.bind(this._dragBy, this), this._kinetic = new KineticController(this._dragBy.bind(this),
Util.bind(this._kineticStop, this)); this._kineticStop.bind(this));
messageManager.addMessageListener("Browser:ContextMenu", this); messageManager.addMessageListener("Browser:ContextMenu", this);
} }

Просмотреть файл

@ -46,12 +46,6 @@
Cu.import("resource://gre/modules/Geometry.jsm"); Cu.import("resource://gre/modules/Geometry.jsm");
let Util = { let Util = {
bind: function bind(f, thisObj) {
return function() {
return f.apply(thisObj, arguments);
};
},
/** printf-like dump function */ /** printf-like dump function */
dumpf: function dumpf(str) { dumpf: function dumpf(str) {
let args = arguments; let args = arguments;

Просмотреть файл

@ -415,70 +415,3 @@ let ContentScroll = {
ContentScroll.init(); ContentScroll.init();
function PromptRemoter() {
addEventListener("DOMWindowCreated", this, false);
}
PromptRemoter.prototype = {
handleEvent: function handleEvent(aEvent) {
var window = aEvent.originalTarget.defaultView.content;
// Need to make sure we are called on what we care about -
// content windows. DOMWindowCreated is called on *all* HTMLDocuments,
// some of which belong to ChromeWindows or lack defaultView.content
// altogether.
//
// Note about the syntax used here: |"wrappedJSObject" in window|
// will silently fail, without even letting us catch it as an
// exception, and checking in the way that we do check in some
// cases still throws an exception; see bug 582108 about both.
try {
if (!window || !window.wrappedJSObject) {
return;
}
}
catch(e) {
return;
}
function bringTabToFront() {
let event = window.document.createEvent("Events");
event.initEvent("DOMWillOpenModalDialog", true, false);
window.dispatchEvent(event);
}
function informClosedFrontTab() {
let event = window.document.createEvent("Events");
event.initEvent("DOMModalDialogClosed", true, false);
window.dispatchEvent(event);
}
window.wrappedJSObject.alert = function(aMessage) {
bringTabToFront();
sendSyncMessage("Prompt:Alert", {
message: aMessage
});
informClosedFrontTab();
}
window.wrappedJSObject.confirm = function(aMessage) {
bringTabToFront();
return sendSyncMessage("Prompt:Confirm", {
message: aMessage
});
informClosedFrontTab();
}
window.wrappedJSObject.prompt = function(aText, aValue) {
bringTabToFront();
return sendSyncMessage("Prompt:Prompt", {
text: aText,
value: aValue
});
informClosedFrontTab();
}
},
};
new PromptRemoter();

Просмотреть файл

@ -146,16 +146,6 @@
} }
break; break;
case "Prompt:Alert":
alert(aMessage.json.message);
break;
case "Prompt:Confirm":
return confirm(aMessage.json.message);
case "Prompt:Prompt":
return prompt(aMessage.json.text, aMessage.json.value);
case "MozScrolledAreaChanged": case "MozScrolledAreaChanged":
this._widthInCSSPx = aMessage.json.width; this._widthInCSSPx = aMessage.json.width;
this._heightInCSSPx = aMessage.json.height; this._heightInCSSPx = aMessage.json.height;
@ -553,11 +543,6 @@
this.messageManager.addMessageListener("pagehide", this); this.messageManager.addMessageListener("pagehide", this);
this.messageManager.addMessageListener("DOMPopupBlocked", this); this.messageManager.addMessageListener("DOMPopupBlocked", this);
// Prompt remoting
["Alert", "Confirm", "Prompt"].forEach(function(name) {
this.messageManager.addMessageListener("Prompt:Alert" + name, this);
}, this);
this.messageManager.addMessageListener("MozScrolledAreaChanged", this); this.messageManager.addMessageListener("MozScrolledAreaChanged", this);
this._webProgress._init(); this._webProgress._init();

Просмотреть файл

@ -655,6 +655,9 @@ var BrowserUI = {
Elements.panelUI.hidden = false; Elements.panelUI.hidden = false;
Elements.contentShowing.setAttribute("disabled", "true"); Elements.contentShowing.setAttribute("disabled", "true");
if (this.activePanel)
this.activePanel = null;
if (aPage != undefined) if (aPage != undefined)
this.switchPane(aPage); this.switchPane(aPage);
}, },
@ -694,8 +697,8 @@ var BrowserUI = {
} }
// Check active panel // Check active panel
if (BrowserUI.activePanel) { if (this.activePanel) {
BrowserUI.activePanel = null; this.activePanel = null;
return; return;
} }
@ -2427,7 +2430,7 @@ var SharingUI = {
button.setAttribute("label", handler.name); button.setAttribute("label", handler.name);
button.addEventListener("command", function() { button.addEventListener("command", function() {
SharingUI.hide(); SharingUI.hide();
handler.callback(aURL, aTitle); handler.callback(aURL || "", aTitle || "");
}, false); }, false);
bbox.appendChild(button); bbox.appendChild(button);
}); });
@ -2444,7 +2447,7 @@ var SharingUI = {
{ {
name: "Email", name: "Email",
callback: function callback(aURL, aTitle) { callback: function callback(aURL, aTitle) {
let url = "mailto:?subject=" + encodeURIComponent(aTitle || "") + let url = "mailto:?subject=" + encodeURIComponent(aTitle) +
"&body=" + encodeURIComponent(aURL); "&body=" + encodeURIComponent(aURL);
let uri = Services.io.newURI(url, null, null); let uri = Services.io.newURI(url, null, null);
let extProtocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"] let extProtocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]

Просмотреть файл

@ -1317,11 +1317,13 @@ ContentCustomClicker.prototype = {
this._dispatchMouseEvent("Browser:MouseCancel"); this._dispatchMouseEvent("Browser:MouseCancel");
const kDoubleClickRadius = 32; const kDoubleClickRadius = 100;
let maxRadius = kDoubleClickRadius * getBrowser().scale; let maxRadius = kDoubleClickRadius * getBrowser().scale;
let isClickInRadius = (Math.abs(aX1 - aX2) < maxRadius && Math.abs(aY1 - aY2) < maxRadius); let dx = aX2 - aX1;
if (isClickInRadius) let dy = aY1 - aY2;
if (dx*dx + dy*dy < maxRadius*maxRadius)
this._dispatchMouseEvent("Browser:ZoomToPoint", aX1, aY1); this._dispatchMouseEvent("Browser:ZoomToPoint", aX1, aY1);
}, },

Просмотреть файл

@ -229,13 +229,12 @@
#ifdef MOZ_PLATFORM_MAEMO #ifdef MOZ_PLATFORM_MAEMO
<toolbarbutton id="tool-app-switch" class="button-image" oncommand="BrowserUI.switchTask();"/> <toolbarbutton id="tool-app-switch" class="button-image" oncommand="BrowserUI.switchTask();"/>
#endif #endif
<hbox id="urlbar-container" flex="1"> <hbox id="urlbar-container" flex="1" observes="bcast_urlbarState">
<box id="identity-box" <box id="identity-box"
onclick="getIdentityHandler().handleIdentityButtonEvent(event);" onclick="getIdentityHandler().handleIdentityButtonEvent(event);"
onkeypress="getIdentityHandler().handleIdentityButtonEvent(event);"> onkeypress="getIdentityHandler().handleIdentityButtonEvent(event);">
<box id="urlbar-image-box" mousethrough="always" observes="bcast_urlbarState"> <box id="urlbar-image-box" mousethrough="always">
<image id="urlbar-throbber"/> <image id="urlbar-throbber"/>
<image id="urlbar-magnifier" hidden="true"/>
<image id="urlbar-favicon" hidden="true"/> <image id="urlbar-favicon" hidden="true"/>
</box> </box>
</box> </box>
@ -258,10 +257,12 @@
<toolbarbutton id="tool-reload" class="urlbar-cap-button" <toolbarbutton id="tool-reload" class="urlbar-cap-button"
oncommand="CommandUpdater.doCommand(event.shiftKey ? 'cmd_forceReload' : 'cmd_reload');"/> oncommand="CommandUpdater.doCommand(event.shiftKey ? 'cmd_forceReload' : 'cmd_reload');"/>
<toolbarbutton id="tool-stop" class="urlbar-cap-button" command="cmd_stop"/> <toolbarbutton id="tool-stop" class="urlbar-cap-button" command="cmd_stop"/>
<toolbarbutton id="tool-go" class="urlbar-cap-button" command="cmd_go"/> <toolbarbutton id="tool-search" class="urlbar-cap-button" command="cmd_opensearch"/>
</hbox> </hbox>
</hbox> </hbox>
#ifdef MOZ_PLATFORM_MAEMO
<toolbarbutton id="tool-app-close" class="urlbar-button button-image" command="cmd_close"/> <toolbarbutton id="tool-app-close" class="urlbar-button button-image" command="cmd_close"/>
#endif
</toolbar> </toolbar>
</box> </box>
</box> </box>

Просмотреть файл

@ -2,7 +2,7 @@
dump("###################################### content loaded\n"); dump("###################################### content loaded\n");
// how many milliseconds before the mousedown and the overlay of an element // how many milliseconds before the mousedown and the overlay of an element
const kTapOverlayTimeout = 200; const kTapOverlayTimeout = 300;
let Cc = Components.classes; let Cc = Components.classes;
let Ci = Components.interfaces; let Ci = Components.interfaces;
@ -392,7 +392,7 @@ Content.prototype = {
// We add a few milliseconds because of how the InputHandler wait before // We add a few milliseconds because of how the InputHandler wait before
// dispatching a single click (default: 500) // dispatching a single click (default: 500)
this._contextTimeout.once(500 + 200, function() { this._contextTimeout.once(500 + kTapOverlayTimeout, function() {
let event = content.document.createEvent("PopupEvents"); let event = content.document.createEvent("PopupEvents");
event.initEvent("contextmenu", true, true); event.initEvent("contextmenu", true, true);
element.dispatchEvent(event); element.dispatchEvent(event);

Просмотреть файл

@ -128,7 +128,7 @@ let WeaveGlue = {
sync.collapsed = !loggedIn; sync.collapsed = !loggedIn;
// Check the lock on a timeout because it's set just after notifying // Check the lock on a timeout because it's set just after notifying
setTimeout(Weave.Utils.bind2(this, function() { setTimeout(function() {
// Prevent certain actions when the service is locked // Prevent certain actions when the service is locked
if (Weave.Service.locked) { if (Weave.Service.locked) {
connect.firstChild.disabled = true; connect.firstChild.disabled = true;
@ -140,7 +140,7 @@ let WeaveGlue = {
sync.firstChild.disabled = false; sync.firstChild.disabled = false;
connect.setAttribute("title", syncStr.get("disconnected.label")); connect.setAttribute("title", syncStr.get("disconnected.label"));
} }
}), 0); }, 0);
// Move the disconnect and sync settings out to make connect the last item // Move the disconnect and sync settings out to make connect the last item
let parent = connect.parentNode; let parent = connect.parentNode;

Просмотреть файл

@ -61,6 +61,7 @@ contract @mozilla.org/helperapplauncherdialog;1 {e9d277a0-268a-4ec2-bb8c-10fdf3e
component {9a61149b-2276-4a0a-b79c-be994ad106cf} PromptService.js component {9a61149b-2276-4a0a-b79c-be994ad106cf} PromptService.js
contract @mozilla.org/prompter;1 {9a61149b-2276-4a0a-b79c-be994ad106cf} contract @mozilla.org/prompter;1 {9a61149b-2276-4a0a-b79c-be994ad106cf}
contract @mozilla.org/embedcomp/prompt-service;1 {9a61149b-2276-4a0a-b79c-be994ad106cf} contract @mozilla.org/embedcomp/prompt-service;1 {9a61149b-2276-4a0a-b79c-be994ad106cf}
category wakeup-request PromptService @mozilla.org/embedcomp/prompt-service;1,nsIPromptService,getService,Prompt:Call
# BrowserCLH.js # BrowserCLH.js
component {be623d20-d305-11de-8a39-0800200c9a66} BrowserCLH.js component {be623d20-d305-11de-8a39-0800200c9a66} BrowserCLH.js

Просмотреть файл

@ -38,7 +38,53 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
var gPromptService = null;
function PromptService() { function PromptService() {
// Depending on if we are in the parent or child, prepare to remote
// certain calls
var appInfo = Cc["@mozilla.org/xre/app-info;1"];
if (!appInfo || appInfo.getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
// Parent process
this.inContentProcess = false;
// Used for wakeups service. FIXME: clean up with bug 593407
this.wrappedJSObject = this;
// Setup listener for child messages. We don't need to call
// addMessageListener as the wakeup service will do that for us.
this.receiveMessage = function(aMessage) {
var json = aMessage.json;
switch (aMessage.name) {
case "Prompt:Call":
// List of methods we remote - to check against malicious data.
// For example, it would be dangerous to allow content to show
// auth prompts. We allow only the methods that web content
// is allowed to show.
const ALL_METHODS = ['alert', 'confirm', 'prompt'];
var method = aMessage.json.method;
if (ALL_METHODS.indexOf(method) == -1)
throw 'PromptServiceRemoter received an invalid method';
var arguments = aMessage.json.arguments;
arguments.unshift(null); // No need for window, child is already on top
// (see mobile browser's PromptService.js)
var ret = this[method].apply(this, arguments);
// Return multiple return values in objects of form { value: ... },
// and also with the actual return value at the end
arguments.push(ret);
return arguments;
}
};
} else {
// Child process
this.inContentProcess = true;
this.messageManager = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender);
}
gPromptService = this;
} }
PromptService.prototype = { PromptService.prototype = {
@ -63,7 +109,7 @@ PromptService.prototype = {
} }
let doc = this.getDocument(); let doc = this.getDocument();
if (!doc) { if (!doc && !this.inContentProcess) {
let fallback = this._getFallbackService(); let fallback = this._getFallbackService();
return fallback.getPrompt(domWin, iid); return fallback.getPrompt(domWin, iid);
} }
@ -73,7 +119,7 @@ PromptService.prototype = {
}, },
/* ---------- private memebers ---------- */ /* ---------- private memebers ---------- */
_getFallbackService: function _getFallbackService() { _getFallbackService: function _getFallbackService() {
return Components.classesByID["{7ad1b327-6dfa-46ec-9234-f2a620ea7e00}"] return Components.classesByID["{7ad1b327-6dfa-46ec-9234-f2a620ea7e00}"]
.getService(Ci.nsIPromptService); .getService(Ci.nsIPromptService);
@ -87,6 +133,35 @@ PromptService.prototype = {
// nsIPromptService and nsIPromptService2 methods proxy to our Prompt class // nsIPromptService and nsIPromptService2 methods proxy to our Prompt class
// if we can show in-document popups, or to the fallback service otherwise. // if we can show in-document popups, or to the fallback service otherwise.
callProxy: function(aMethod, aArguments) { callProxy: function(aMethod, aArguments) {
if (this.inContentProcess) {
// Bring this tab to the front, so prompt appears on the right tab
var window = aArguments[0];
if (window && window.document) {
var event = window.document.createEvent("Events");
event.initEvent("DOMWillOpenModalDialog", true, false);
window.dispatchEvent(event);
}
// Send message to parent
var json = { method: aMethod,
arguments: Array.prototype.slice.call(aArguments, 1) };
// We send all prompts as sync, even alert (which has no important
// return value), as otherwise program flow will continue, and the
// script can theoretically show several alerts at once. In particular
// this can lead to a bug where you cannot click the earlier one, which
// is now hidden by a new one (and Fennec is helplessly frozen).
var response =
this.messageManager.sendSyncMessage("Prompt:Call", json)[0];
// Args copying - for methods that have out values
const ARGS_COPY_MAP = {
'prompt': [3,5],
};
if (ARGS_COPY_MAP[aMethod]) {
ARGS_COPY_MAP[aMethod].forEach(function(i) { aArguments[i].value = response[i].value; });
}
return response.pop(); // final return value was given at the end
}
let doc = this.getDocument(); let doc = this.getDocument();
if (!doc) { if (!doc) {
let fallback = this._getFallbackService(); let fallback = this._getFallbackService();
@ -259,6 +334,14 @@ Prompt.prototype = {
/* ---------- nsIPrompt ---------- */ /* ---------- nsIPrompt ---------- */
alert: function alert(aTitle, aText) { alert: function alert(aTitle, aText) {
// In addition to the remoting above, C++ can directly request this
// kind of prompt, so we remote that as well. This can happen, for
// example, if an invalid scheme is entered (e.g. garbage://something).
// That shows an alert() through this code here.
if (gPromptService.inContentProcess) {
return gPromptService.callProxy("alert", ['Alert'].concat(Array.prototype.slice.call(arguments, 0)));
}
let dialog = this.openDialog("chrome://browser/content/prompt/alert.xul", null); let dialog = this.openDialog("chrome://browser/content/prompt/alert.xul", null);
let doc = this._doc; let doc = this._doc;
doc.getElementById("prompt-alert-title").value = aTitle; doc.getElementById("prompt-alert-title").value = aTitle;

Просмотреть файл

@ -55,6 +55,8 @@ toolbarbutton.urlbar-button {
-moz-margin-start: 8px; /* core spacing */ -moz-margin-start: 8px; /* core spacing */
} }
%ifdef MOZ_PLATFORM_MAEMO
/* MAEMO only */
#tool-app-switch { #tool-app-switch {
margin-top: -8px; /* force the button to go to the edge */ margin-top: -8px; /* force the button to go to the edge */
margin-bottom: -8px; /* force the button to go to the edge */ margin-bottom: -8px; /* force the button to go to the edge */
@ -76,6 +78,7 @@ toolbarbutton.urlbar-button {
visibility: visible; visibility: visible;
} }
/* MAEMO only */
#tool-app-close { #tool-app-close {
margin-top: -8px; /* force the button to go to the edge */ margin-top: -8px; /* force the button to go to the edge */
margin-bottom: -8px; /* force the button to go to the edge */ margin-bottom: -8px; /* force the button to go to the edge */
@ -111,6 +114,7 @@ toolbarbutton.urlbar-button {
#toolbar-main[dialog="true"] #tool-app-close:-moz-locale-dir(rtl) { #toolbar-main[dialog="true"] #tool-app-close:-moz-locale-dir(rtl) {
list-style-image: url("chrome://browser/skin/images/task-back-rtl-40.png"); list-style-image: url("chrome://browser/skin/images/task-back-rtl-40.png");
} }
%endif
/* URL bar cap buttons */ /* URL bar cap buttons */
toolbarbutton.urlbar-cap-button { toolbarbutton.urlbar-cap-button {
@ -121,14 +125,6 @@ toolbarbutton.urlbar-cap-button {
-moz-box-flex: 1; -moz-box-flex: 1;
} }
#tool-go {
list-style-image: url("chrome://browser/skin/images/go-30.png");
}
#tool-go:-moz-locale-dir(rtl) {
list-style-image: url("chrome://browser/skin/images/go-rtl-30.png");
}
#tool-reload { #tool-reload {
list-style-image: url("chrome://browser/skin/images/reload-30.png"); list-style-image: url("chrome://browser/skin/images/reload-30.png");
} }
@ -137,12 +133,16 @@ toolbarbutton.urlbar-cap-button {
list-style-image: url("chrome://browser/skin/images/stop-30.png"); list-style-image: url("chrome://browser/skin/images/stop-30.png");
} }
#urlbar-icons[mode="loading"] > #tool-go, #tool-search {
list-style-image: url("chrome://browser/skin/images/navigation-magnifier-30.png");
}
#urlbar-icons[mode="loading"] > #tool-search,
#urlbar-icons[mode="loading"] > #tool-reload { #urlbar-icons[mode="loading"] > #tool-reload {
visibility: collapse; visibility: collapse;
} }
#urlbar-icons[mode="view"] > #tool-go, #urlbar-icons[mode="view"] > #tool-search,
#urlbar-icons[mode="view"] > #tool-stop { #urlbar-icons[mode="view"] > #tool-stop {
visibility: collapse; visibility: collapse;
} }
@ -219,23 +219,14 @@ toolbarbutton.urlbar-cap-button {
background: url("images/rightcapSSL-active-64.png"); background: url("images/rightcapSSL-active-64.png");
} }
#urlbar-image-box[mode=edit] > #urlbar-favicon, #urlbar-container[mode="edit"] > #identity-box {
#urlbar-image-box[mode=edit] > #urlbar-throbber { visibility: collapse;
display: none;
} }
#urlbar-throbber[loading] { #urlbar-throbber[loading] {
list-style-image: url("chrome://browser/skin/images/throbber.png"); list-style-image: url("chrome://browser/skin/images/throbber.png");
} }
#urlbar-magnifier {
list-style-image: url("chrome://browser/skin/images/navigation-magnifier-30.png");
}
#urlbar-image-box[mode=edit] > #urlbar-magnifier {
display: block !important;
}
#urlbar-favicon { #urlbar-favicon {
width: 32px; width: 32px;
height: 32px; height: 32px;
@ -267,7 +258,20 @@ toolbarbutton.urlbar-cap-button {
#urlbar-edit > hbox > hbox > .textbox-input { #urlbar-edit > hbox > hbox > .textbox-input {
min-height: 60px; min-height: 60px;
text-indent: 3px; text-indent: 8px;
}
#urlbar-container[mode="edit"] > #urlbar-edit {
-moz-border-start: 3px solid #262629 !important;
-moz-border-radius-topleft: 10px;
-moz-border-radius-bottomleft: 10px;
}
#urlbar-container[mode="edit"] > #urlbar-edit:-moz-locale-dir(rtl) {
-moz-border-radius-topleft: 0;
-moz-border-radius-bottomleft: 0;
-moz-border-radius-topright: 10px;
-moz-border-radius-bottomright: 10px;
} }
/* make sure this endcap matches the other endcap */ /* make sure this endcap matches the other endcap */

Двоичные данные
mobile/themes/core/images/go-30.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 699 B

Двоичные данные
mobile/themes/core/images/go-rtl-30.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 730 B

Просмотреть файл

@ -10,7 +10,7 @@ chrome.jar:
skin/firstRun.css (firstRun.css) skin/firstRun.css (firstRun.css)
skin/header.css (header.css) skin/header.css (header.css)
skin/platform.css (platform.css) skin/platform.css (platform.css)
skin/browser.css (browser.css) * skin/browser.css (browser.css)
skin/notification.css (notification.css) skin/notification.css (notification.css)
% override chrome://global/skin/about.css chrome://browser/skin/about.css % override chrome://global/skin/about.css chrome://browser/skin/about.css
@ -42,8 +42,6 @@ chrome.jar:
skin/images/folder-32.png (images/folder-32.png) skin/images/folder-32.png (images/folder-32.png)
skin/images/stop-30.png (images/stop-30.png) skin/images/stop-30.png (images/stop-30.png)
skin/images/reload-30.png (images/reload-30.png) skin/images/reload-30.png (images/reload-30.png)
skin/images/go-30.png (images/go-30.png)
skin/images/go-rtl-30.png (images/go-rtl-30.png)
skin/images/alert-addons-30.png (images/alert-addons-30.png) skin/images/alert-addons-30.png (images/alert-addons-30.png)
skin/images/alert-downloads-30.png (images/alert-downloads-30.png) skin/images/alert-downloads-30.png (images/alert-downloads-30.png)
skin/images/addons-default-64.png (images/addons-default-64.png) skin/images/addons-default-64.png (images/addons-default-64.png)