forked from mirrors/gecko-dev
Bug 1589182 - remove mobile/android/extensions/ and /mobile/android/chrome/content (Fennec leftovers); r=snorp,agi
Remove mobile/android/extensions/ and /mobile/android/chrome/content from mozilla-central (Fennec leftovers) Differential Revision: https://phabricator.services.mozilla.com/D51194 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
407ce752f5
commit
dea46a2eed
114 changed files with 0 additions and 20626 deletions
|
|
@ -222,9 +222,6 @@ mobile/android/tests/browser/chrome/tp5/
|
||||||
mobile/android/app/mobile.js
|
mobile/android/app/mobile.js
|
||||||
mobile/android/app/geckoview-prefs.js
|
mobile/android/app/geckoview-prefs.js
|
||||||
|
|
||||||
# Uses `#expand`
|
|
||||||
mobile/android/chrome/content/about.js
|
|
||||||
|
|
||||||
# Not much JS to lint and non-standard at that
|
# Not much JS to lint and non-standard at that
|
||||||
mobile/android/installer/
|
mobile/android/installer/
|
||||||
mobile/android/locales/
|
mobile/android/locales/
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
"rules": {
|
|
||||||
"complexity": ["error", 20],
|
|
||||||
|
|
||||||
// Disabled stuff
|
|
||||||
"no-console": 0, // TODO: Can we use console?
|
|
||||||
"no-cond-assign": 0,
|
|
||||||
"no-fallthrough": 0,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,857 +0,0 @@
|
||||||
// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
|
||||||
/* 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";
|
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"PageActions",
|
|
||||||
"resource://gre/modules/PageActions.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Define service devices. We should consider moving these to their respective
|
|
||||||
// JSM files, but we left them here to allow for better lazy JSM loading.
|
|
||||||
var rokuDevice = {
|
|
||||||
id: "roku:ecp",
|
|
||||||
target: "roku:ecp",
|
|
||||||
factory: function(aService) {
|
|
||||||
const { RokuApp } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/RokuApp.jsm"
|
|
||||||
);
|
|
||||||
return new RokuApp(aService);
|
|
||||||
},
|
|
||||||
types: ["video/mp4"],
|
|
||||||
extensions: ["mp4"],
|
|
||||||
};
|
|
||||||
|
|
||||||
var mediaPlayerDevice = {
|
|
||||||
id: "media:router",
|
|
||||||
target: "media:router",
|
|
||||||
factory: function(aService) {
|
|
||||||
const { MediaPlayerApp } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/MediaPlayerApp.jsm"
|
|
||||||
);
|
|
||||||
return new MediaPlayerApp(aService);
|
|
||||||
},
|
|
||||||
types: ["video/mp4", "video/webm", "application/x-mpegurl"],
|
|
||||||
extensions: ["mp4", "webm", "m3u", "m3u8"],
|
|
||||||
init: function() {
|
|
||||||
GlobalEventDispatcher.registerListener(this, [
|
|
||||||
"MediaPlayer:Added",
|
|
||||||
"MediaPlayer:Changed",
|
|
||||||
"MediaPlayer:Removed",
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
onEvent: function(event, data, callback) {
|
|
||||||
if (event === "MediaPlayer:Added") {
|
|
||||||
let service = this.toService(data);
|
|
||||||
SimpleServiceDiscovery.addService(service);
|
|
||||||
} else if (event === "MediaPlayer:Changed") {
|
|
||||||
let service = this.toService(data);
|
|
||||||
SimpleServiceDiscovery.updateService(service);
|
|
||||||
} else if (event === "MediaPlayer:Removed") {
|
|
||||||
SimpleServiceDiscovery.removeService(data.id);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
toService: function(display) {
|
|
||||||
// Convert the native data into something matching what is created in _processService()
|
|
||||||
return {
|
|
||||||
location: display.location,
|
|
||||||
target: "media:router",
|
|
||||||
friendlyName: display.friendlyName,
|
|
||||||
uuid: display.uuid,
|
|
||||||
manufacturer: display.manufacturer,
|
|
||||||
modelName: display.modelName,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var CastingApps = {
|
|
||||||
_castMenuId: -1,
|
|
||||||
_blocked: null,
|
|
||||||
_bound: null,
|
|
||||||
_interval: 120 * 1000, // 120 seconds
|
|
||||||
|
|
||||||
init: function ca_init() {
|
|
||||||
if (!this.isCastingEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register targets
|
|
||||||
SimpleServiceDiscovery.registerDevice(rokuDevice);
|
|
||||||
|
|
||||||
// MediaPlayerDevice will notify us any time the native device list changes.
|
|
||||||
mediaPlayerDevice.init();
|
|
||||||
SimpleServiceDiscovery.registerDevice(mediaPlayerDevice);
|
|
||||||
|
|
||||||
// Search for devices continuously
|
|
||||||
SimpleServiceDiscovery.search(this._interval);
|
|
||||||
|
|
||||||
this._castMenuId = NativeWindow.contextmenus.add(
|
|
||||||
Strings.browser.GetStringFromName("contextmenu.sendToDevice"),
|
|
||||||
this.filterCast,
|
|
||||||
this.handleContextMenu.bind(this)
|
|
||||||
);
|
|
||||||
|
|
||||||
GlobalEventDispatcher.registerListener(this, [
|
|
||||||
"Casting:Play",
|
|
||||||
"Casting:Pause",
|
|
||||||
"Casting:Stop",
|
|
||||||
]);
|
|
||||||
|
|
||||||
Services.obs.addObserver(this, "ssdp-service-found");
|
|
||||||
Services.obs.addObserver(this, "ssdp-service-lost");
|
|
||||||
Services.obs.addObserver(this, "application-background");
|
|
||||||
Services.obs.addObserver(this, "application-foreground");
|
|
||||||
|
|
||||||
BrowserApp.deck.addEventListener("TabSelect", this, true);
|
|
||||||
BrowserApp.deck.addEventListener("pageshow", this, true);
|
|
||||||
BrowserApp.deck.addEventListener("playing", this, true);
|
|
||||||
BrowserApp.deck.addEventListener("ended", this, true);
|
|
||||||
BrowserApp.deck.addEventListener("MozAutoplayMediaBlocked", this, true);
|
|
||||||
// Note that the XBL binding is untrusted
|
|
||||||
BrowserApp.deck.addEventListener(
|
|
||||||
"MozNoControlsVideoBindingAttached",
|
|
||||||
this,
|
|
||||||
true,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
_mirrorStarted: function(stopMirrorCallback) {
|
|
||||||
this.stopMirrorCallback = stopMirrorCallback;
|
|
||||||
NativeWindow.menu.update(this.mirrorStartMenuId, { visible: false });
|
|
||||||
NativeWindow.menu.update(this.mirrorStopMenuId, { visible: true });
|
|
||||||
},
|
|
||||||
|
|
||||||
serviceAdded: function(aService) {},
|
|
||||||
|
|
||||||
serviceLost: function(aService) {},
|
|
||||||
|
|
||||||
isCastingEnabled: function isCastingEnabled() {
|
|
||||||
return Services.prefs.getBoolPref("browser.casting.enabled");
|
|
||||||
},
|
|
||||||
|
|
||||||
onEvent: function(event, message, callback) {
|
|
||||||
switch (event) {
|
|
||||||
case "Casting:Play":
|
|
||||||
if (this.session && this.session.remoteMedia.status == "paused") {
|
|
||||||
this.session.remoteMedia.play();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "Casting:Pause":
|
|
||||||
if (this.session && this.session.remoteMedia.status == "started") {
|
|
||||||
this.session.remoteMedia.pause();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "Casting:Stop":
|
|
||||||
if (this.session) {
|
|
||||||
this.closeExternal();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
observe: function(aSubject, aTopic, aData) {
|
|
||||||
switch (aTopic) {
|
|
||||||
case "ssdp-service-found":
|
|
||||||
this.serviceAdded(SimpleServiceDiscovery.findServiceForID(aData));
|
|
||||||
break;
|
|
||||||
case "ssdp-service-lost":
|
|
||||||
this.serviceLost(SimpleServiceDiscovery.findServiceForID(aData));
|
|
||||||
break;
|
|
||||||
case "application-background":
|
|
||||||
// Turn off polling while in the background
|
|
||||||
this._interval = SimpleServiceDiscovery.search(0);
|
|
||||||
SimpleServiceDiscovery.stopSearch();
|
|
||||||
break;
|
|
||||||
case "application-foreground":
|
|
||||||
// Turn polling on when app comes back to foreground
|
|
||||||
SimpleServiceDiscovery.search(this._interval);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEvent: function(aEvent) {
|
|
||||||
switch (aEvent.type) {
|
|
||||||
case "TabSelect": {
|
|
||||||
let tab = BrowserApp.getTabForBrowser(aEvent.target);
|
|
||||||
this._updatePageActionForTab(tab, aEvent);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "pageshow": {
|
|
||||||
let tab = BrowserApp.getTabForWindow(aEvent.originalTarget.defaultView);
|
|
||||||
this._updatePageActionForTab(tab, aEvent);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "playing":
|
|
||||||
case "ended": {
|
|
||||||
let video = aEvent.target;
|
|
||||||
if (video instanceof HTMLVideoElement) {
|
|
||||||
// If playing, send the <video>, but if ended we send nothing to shutdown the pageaction
|
|
||||||
this._updatePageActionForVideo(
|
|
||||||
aEvent.type === "playing" ? video : null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "MozAutoplayMediaBlocked": {
|
|
||||||
if (this._bound && this._bound.has(aEvent.target)) {
|
|
||||||
aEvent.target.dispatchEvent(
|
|
||||||
new aEvent.target.ownerGlobal.CustomEvent(
|
|
||||||
"MozNoControlsBlockedVideo"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (!this._blocked) {
|
|
||||||
this._blocked = new WeakMap();
|
|
||||||
}
|
|
||||||
this._blocked.set(aEvent.target, true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "MozNoControlsVideoBindingAttached": {
|
|
||||||
if (!this._bound) {
|
|
||||||
this._bound = new WeakMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let video = this._findVideoFromEventTarget(aEvent.target);
|
|
||||||
if (!video) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._bound.set(video, true);
|
|
||||||
if (this._blocked && this._blocked.has(video)) {
|
|
||||||
this._blocked.delete(video);
|
|
||||||
video.dispatchEvent(
|
|
||||||
new video.ownerGlobal.CustomEvent("MozNoControlsBlockedVideo")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_findVideoFromEventTarget(aTarget) {
|
|
||||||
if (
|
|
||||||
typeof ShadowRoot !== "undefined" &&
|
|
||||||
aTarget.parentNode instanceof ShadowRoot &&
|
|
||||||
aTarget.parentNode.host instanceof HTMLVideoElement
|
|
||||||
) {
|
|
||||||
// aTarget is <div class="videocontrols"> inside UA Widget Shadow Root
|
|
||||||
return aTarget.parentNode.host;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aTarget instanceof HTMLVideoElement) {
|
|
||||||
// aTarget is <video>.
|
|
||||||
return aTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
_sendEventToVideo: function _sendEventToVideo(aElement, aData) {
|
|
||||||
let event = aElement.ownerDocument.createEvent("CustomEvent");
|
|
||||||
event.initCustomEvent(
|
|
||||||
"media-videoCasting",
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
JSON.stringify(aData)
|
|
||||||
);
|
|
||||||
aElement.dispatchEvent(event);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleVideoBindingAttached: function handleVideoBindingAttached(
|
|
||||||
aTab,
|
|
||||||
aEvent
|
|
||||||
) {
|
|
||||||
// Let's figure out if we have everything needed to cast a video. The binding
|
|
||||||
// defaults to |false| so we only need to send an event if |true|.
|
|
||||||
let video = this._findVideoFromEventTarget(aEvent.target);
|
|
||||||
if (!video) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SimpleServiceDiscovery.services.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.getVideo(video, 0, 0, aBundle => {
|
|
||||||
// Let the binding know casting is allowed
|
|
||||||
if (aBundle) {
|
|
||||||
this._sendEventToVideo(aBundle.element, { allow: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleVideoBindingCast: function handleVideoBindingCast(aTab, aEvent) {
|
|
||||||
// The binding wants to start a casting session
|
|
||||||
let video = this._findVideoFromEventTarget(aEvent.target);
|
|
||||||
if (!video) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close an existing session first. closeExternal has checks for an exsting
|
|
||||||
// session and handles remote and video binding shutdown.
|
|
||||||
this.closeExternal();
|
|
||||||
|
|
||||||
// Start the new session
|
|
||||||
UITelemetry.addEvent("cast.1", "button", null);
|
|
||||||
this.openExternal(video, 0, 0);
|
|
||||||
},
|
|
||||||
|
|
||||||
makeURI: function makeURI(aURL, aOriginCharset, aBaseURI) {
|
|
||||||
return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
|
|
||||||
},
|
|
||||||
|
|
||||||
allowableExtension: function(aURI, aExtensions) {
|
|
||||||
return (
|
|
||||||
aURI instanceof Ci.nsIURL && aExtensions.includes(aURI.fileExtension)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
allowableMimeType: function(aType, aTypes) {
|
|
||||||
return aTypes.includes(aType);
|
|
||||||
},
|
|
||||||
|
|
||||||
// This method will look at the aElement (or try to find a video at aX, aY) that has
|
|
||||||
// a castable source. If found, aCallback will be called with a JSON meta bundle. If
|
|
||||||
// no castable source was found, aCallback is called with null.
|
|
||||||
getVideo: function(aElement, aX, aY, aCallback) {
|
|
||||||
let extensions = SimpleServiceDiscovery.getSupportedExtensions();
|
|
||||||
let types = SimpleServiceDiscovery.getSupportedMimeTypes();
|
|
||||||
|
|
||||||
// Fast path: Is the given element a video element?
|
|
||||||
if (aElement instanceof HTMLVideoElement) {
|
|
||||||
// If we found a video element, no need to look further, even if no
|
|
||||||
// castable video source is found.
|
|
||||||
this._getVideo(aElement, types, extensions, aCallback);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maybe this is an overlay, with the video element under it.
|
|
||||||
// Use the (x, y) location to guess at a <video> element.
|
|
||||||
|
|
||||||
// The context menu system will keep walking up the DOM giving us a chance
|
|
||||||
// to find an element we match. When it hits <html> things can go BOOM.
|
|
||||||
try {
|
|
||||||
let elements = aElement.ownerDocument.querySelectorAll("video");
|
|
||||||
for (let element of elements) {
|
|
||||||
// Look for a video element contained in the overlay bounds
|
|
||||||
let rect = element.getBoundingClientRect();
|
|
||||||
if (
|
|
||||||
aY >= rect.top &&
|
|
||||||
aX >= rect.left &&
|
|
||||||
aY <= rect.bottom &&
|
|
||||||
aX <= rect.right
|
|
||||||
) {
|
|
||||||
// Once we find a <video> under the overlay, we check it and exit.
|
|
||||||
this._getVideo(element, types, extensions, aCallback);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
},
|
|
||||||
|
|
||||||
_getContentTypeForURI: function(aURI, aElement, aCallback) {
|
|
||||||
let channel;
|
|
||||||
try {
|
|
||||||
let secFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
|
|
||||||
if (aElement.crossOrigin) {
|
|
||||||
secFlags = Ci.nsILoadInfo.SEC_REQUIRE_CORS_DATA_INHERITS;
|
|
||||||
if (aElement.crossOrigin === "use-credentials") {
|
|
||||||
secFlags |= Ci.nsILoadInfo.SEC_COOKIES_INCLUDE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
channel = NetUtil.newChannel({
|
|
||||||
uri: aURI,
|
|
||||||
loadingNode: aElement,
|
|
||||||
securityFlags: secFlags,
|
|
||||||
contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_VIDEO,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
aCallback(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let listener = {
|
|
||||||
onStartRequest: function(request) {
|
|
||||||
switch (channel.responseStatus) {
|
|
||||||
case 301:
|
|
||||||
case 302:
|
|
||||||
case 303:
|
|
||||||
request.cancel(0);
|
|
||||||
let location = channel.getResponseHeader("Location");
|
|
||||||
CastingApps._getContentTypeForURI(
|
|
||||||
CastingApps.makeURI(location),
|
|
||||||
aElement,
|
|
||||||
aCallback
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
aCallback(channel.contentType);
|
|
||||||
request.cancel(0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onStopRequest: function(request, statusCode) {},
|
|
||||||
onDataAvailable: function(request, stream, offset, count) {},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (channel) {
|
|
||||||
channel.asyncOpen(listener);
|
|
||||||
} else {
|
|
||||||
aCallback(null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Because this method uses a callback, make sure we return ASAP if we know
|
|
||||||
// we have a castable video source.
|
|
||||||
_getVideo: function(aElement, aTypes, aExtensions, aCallback) {
|
|
||||||
// Keep a list of URIs we need for an async mimetype check
|
|
||||||
let asyncURIs = [];
|
|
||||||
|
|
||||||
// Grab the poster attribute from the <video>
|
|
||||||
let posterURL = aElement.poster;
|
|
||||||
|
|
||||||
// First, look to see if the <video> has a src attribute
|
|
||||||
let sourceURL = aElement.src;
|
|
||||||
|
|
||||||
// If empty, try the currentSrc
|
|
||||||
if (!sourceURL) {
|
|
||||||
sourceURL = aElement.currentSrc;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceURL) {
|
|
||||||
// Use the file extension to guess the mime type
|
|
||||||
let sourceURI = this.makeURI(
|
|
||||||
sourceURL,
|
|
||||||
null,
|
|
||||||
this.makeURI(aElement.baseURI)
|
|
||||||
);
|
|
||||||
if (this.allowableExtension(sourceURI, aExtensions)) {
|
|
||||||
aCallback({
|
|
||||||
element: aElement,
|
|
||||||
source: sourceURI.spec,
|
|
||||||
poster: posterURL,
|
|
||||||
sourceURI: sourceURI,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aElement.type) {
|
|
||||||
// Fast sync check
|
|
||||||
if (this.allowableMimeType(aElement.type, aTypes)) {
|
|
||||||
aCallback({
|
|
||||||
element: aElement,
|
|
||||||
source: sourceURI.spec,
|
|
||||||
poster: posterURL,
|
|
||||||
sourceURI: sourceURI,
|
|
||||||
type: aElement.type,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delay the async check until we sync scan all possible URIs
|
|
||||||
asyncURIs.push(sourceURI);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next, look to see if there is a <source> child element that meets
|
|
||||||
// our needs
|
|
||||||
let sourceNodes = aElement.getElementsByTagName("source");
|
|
||||||
for (let sourceNode of sourceNodes) {
|
|
||||||
let sourceURI = this.makeURI(
|
|
||||||
sourceNode.src,
|
|
||||||
null,
|
|
||||||
this.makeURI(sourceNode.baseURI)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Using the type attribute is our ideal way to guess the mime type. Otherwise,
|
|
||||||
// fallback to using the file extension to guess the mime type
|
|
||||||
if (this.allowableExtension(sourceURI, aExtensions)) {
|
|
||||||
aCallback({
|
|
||||||
element: aElement,
|
|
||||||
source: sourceURI.spec,
|
|
||||||
poster: posterURL,
|
|
||||||
sourceURI: sourceURI,
|
|
||||||
type: sourceNode.type,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceNode.type) {
|
|
||||||
// Fast sync check
|
|
||||||
if (this.allowableMimeType(sourceNode.type, aTypes)) {
|
|
||||||
aCallback({
|
|
||||||
element: aElement,
|
|
||||||
source: sourceURI.spec,
|
|
||||||
poster: posterURL,
|
|
||||||
sourceURI: sourceURI,
|
|
||||||
type: sourceNode.type,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delay the async check until we sync scan all possible URIs
|
|
||||||
asyncURIs.push(sourceURI);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper method that walks the array of possible URIs, fetching the mimetype as we go.
|
|
||||||
// As soon as we find a good sourceURL, avoid firing the callback any further
|
|
||||||
var _getContentTypeForURIs = aURIs => {
|
|
||||||
// Do an async fetch to figure out the mimetype of the source video
|
|
||||||
let sourceURI = aURIs.pop();
|
|
||||||
this._getContentTypeForURI(sourceURI, aElement, aType => {
|
|
||||||
if (this.allowableMimeType(aType, aTypes)) {
|
|
||||||
// We found a supported mimetype.
|
|
||||||
aCallback({
|
|
||||||
element: aElement,
|
|
||||||
source: sourceURI.spec,
|
|
||||||
poster: posterURL,
|
|
||||||
sourceURI: sourceURI,
|
|
||||||
type: aType,
|
|
||||||
});
|
|
||||||
} else if (aURIs.length > 0) {
|
|
||||||
// This URI was not a supported mimetype, so let's try the next, if we have more.
|
|
||||||
_getContentTypeForURIs(aURIs);
|
|
||||||
} else {
|
|
||||||
// We were not able to find a supported mimetype.
|
|
||||||
aCallback(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// If we didn't find a good URI directly, let's look using async methods.
|
|
||||||
if (asyncURIs.length > 0) {
|
|
||||||
_getContentTypeForURIs(asyncURIs);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// This code depends on handleVideoBindingAttached setting mozAllowCasting
|
|
||||||
// so we can quickly figure out if the video is castable
|
|
||||||
isVideoCastable: function(aElement, aX, aY) {
|
|
||||||
// Use the flag set when the <video> binding was created as the check
|
|
||||||
if (aElement instanceof HTMLVideoElement) {
|
|
||||||
return aElement.mozAllowCasting;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is called by the context menu system and the system will keep
|
|
||||||
// walking up the DOM giving us a chance to find an element we match.
|
|
||||||
// When it hits <html> things can go BOOM.
|
|
||||||
try {
|
|
||||||
// Maybe this is an overlay, with the video element under it
|
|
||||||
// Use the (x, y) location to guess at a <video> element
|
|
||||||
let elements = aElement.ownerDocument.querySelectorAll("video");
|
|
||||||
for (let element of elements) {
|
|
||||||
// Look for a video element contained in the overlay bounds
|
|
||||||
let rect = element.getBoundingClientRect();
|
|
||||||
if (
|
|
||||||
aY >= rect.top &&
|
|
||||||
aX >= rect.left &&
|
|
||||||
aY <= rect.bottom &&
|
|
||||||
aX <= rect.right
|
|
||||||
) {
|
|
||||||
// Use the flag set when the <video> binding was created as the check
|
|
||||||
return element.mozAllowCasting;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
filterCast: {
|
|
||||||
matches: function(aElement, aX, aY) {
|
|
||||||
// This behavior matches the pageaction: As long as a video is castable,
|
|
||||||
// we can cast it, even if it's already being cast to a device.
|
|
||||||
if (SimpleServiceDiscovery.services.length == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return CastingApps.isVideoCastable(aElement, aX, aY);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
pageAction: {
|
|
||||||
click: function() {
|
|
||||||
// Since this is a pageaction, we use the selected browser
|
|
||||||
let browser = BrowserApp.selectedBrowser;
|
|
||||||
if (!browser) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for a castable <video> that is playing, and start casting it
|
|
||||||
let videos = browser.contentDocument.querySelectorAll("video");
|
|
||||||
for (let video of videos) {
|
|
||||||
if (!video.paused && video.mozAllowCasting) {
|
|
||||||
UITelemetry.addEvent("cast.1", "pageaction", null);
|
|
||||||
CastingApps.openExternal(video, 0, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
_findCastableVideo: function _findCastableVideo(aBrowser) {
|
|
||||||
if (!aBrowser) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan for a <video> being actively cast. Also look for a castable <video>
|
|
||||||
// on the page.
|
|
||||||
let castableVideo = null;
|
|
||||||
let videos = aBrowser.contentDocument.querySelectorAll("video");
|
|
||||||
for (let video of videos) {
|
|
||||||
if (video.mozIsCasting) {
|
|
||||||
// This <video> is cast-active. Break out of loop.
|
|
||||||
return video;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!video.paused && video.mozAllowCasting) {
|
|
||||||
// This <video> is cast-ready. Keep looking so cast-active could be found.
|
|
||||||
castableVideo = video;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Could be null
|
|
||||||
return castableVideo;
|
|
||||||
},
|
|
||||||
|
|
||||||
_updatePageActionForTab: function _updatePageActionForTab(aTab, aEvent) {
|
|
||||||
// We only care about events on the selected tab
|
|
||||||
if (aTab != BrowserApp.selectedTab) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the page action, scanning for a castable <video>
|
|
||||||
this._updatePageAction();
|
|
||||||
},
|
|
||||||
|
|
||||||
_updatePageActionForVideo: function _updatePageActionForVideo(aVideo) {
|
|
||||||
this._updatePageAction(aVideo);
|
|
||||||
},
|
|
||||||
|
|
||||||
_updatePageAction: function _updatePageAction(aVideo) {
|
|
||||||
// Remove any exising pageaction first, in case state changes or we don't have
|
|
||||||
// a castable video
|
|
||||||
if (this.pageAction.id) {
|
|
||||||
PageActions.remove(this.pageAction.id);
|
|
||||||
delete this.pageAction.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!aVideo) {
|
|
||||||
aVideo = this._findCastableVideo(BrowserApp.selectedBrowser);
|
|
||||||
if (!aVideo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only show pageactions if the <video> is from the selected tab
|
|
||||||
if (
|
|
||||||
BrowserApp.selectedTab !=
|
|
||||||
BrowserApp.getTabForWindow(aVideo.ownerGlobal.top)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We check for two state here:
|
|
||||||
// 1. The video is actively being cast
|
|
||||||
// 2. The video is allowed to be cast and is currently playing
|
|
||||||
// Both states have the same action: Show the cast page action
|
|
||||||
if (aVideo.mozIsCasting) {
|
|
||||||
this.pageAction.id = PageActions.add({
|
|
||||||
title: Strings.browser.GetStringFromName("contextmenu.sendToDevice"),
|
|
||||||
icon: "drawable://casting_active",
|
|
||||||
clickCallback: this.pageAction.click,
|
|
||||||
important: true,
|
|
||||||
useTint: false,
|
|
||||||
});
|
|
||||||
} else if (aVideo.mozAllowCasting) {
|
|
||||||
this.pageAction.id = PageActions.add({
|
|
||||||
title: Strings.browser.GetStringFromName("contextmenu.sendToDevice"),
|
|
||||||
icon: "drawable://casting",
|
|
||||||
clickCallback: this.pageAction.click,
|
|
||||||
important: true,
|
|
||||||
useTint: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
prompt: function(aWindow, aCallback, aFilterFunc) {
|
|
||||||
let items = [];
|
|
||||||
let filteredServices = [];
|
|
||||||
SimpleServiceDiscovery.services.forEach(function(aService) {
|
|
||||||
let item = {
|
|
||||||
label: aService.friendlyName,
|
|
||||||
selected: false,
|
|
||||||
};
|
|
||||||
if (!aFilterFunc || aFilterFunc(aService)) {
|
|
||||||
filteredServices.push(aService);
|
|
||||||
items.push(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (items.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let prompt = new Prompt({
|
|
||||||
window: aWindow,
|
|
||||||
title: Strings.browser.GetStringFromName("casting.sendToDevice"),
|
|
||||||
})
|
|
||||||
.setSingleChoiceItems(items)
|
|
||||||
.show(function(data) {
|
|
||||||
let selected = data.button;
|
|
||||||
let service = selected == -1 ? null : filteredServices[selected];
|
|
||||||
if (aCallback) {
|
|
||||||
aCallback(service);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleContextMenu: function(aElement, aX, aY) {
|
|
||||||
UITelemetry.addEvent("action.1", "contextmenu", null, "web_cast");
|
|
||||||
UITelemetry.addEvent("cast.1", "contextmenu", null);
|
|
||||||
this.openExternal(aElement, aX, aY);
|
|
||||||
},
|
|
||||||
|
|
||||||
openExternal: function(aElement, aX, aY) {
|
|
||||||
// Start a second screen media service
|
|
||||||
this.getVideo(aElement, aX, aY, this._openExternal.bind(this));
|
|
||||||
},
|
|
||||||
|
|
||||||
_openExternal: function(aVideo) {
|
|
||||||
if (!aVideo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterFunc(aService) {
|
|
||||||
return (
|
|
||||||
this.allowableExtension(aVideo.sourceURI, aService.extensions) ||
|
|
||||||
this.allowableMimeType(aVideo.type, aService.types)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.prompt(
|
|
||||||
aVideo.element.ownerGlobal,
|
|
||||||
aService => {
|
|
||||||
if (!aService) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we have a player app for the given service
|
|
||||||
let app = SimpleServiceDiscovery.findAppForService(aService);
|
|
||||||
if (!app) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aVideo.element) {
|
|
||||||
aVideo.title = aVideo.element.ownerGlobal.top.document.title;
|
|
||||||
|
|
||||||
// If the video is currently playing on the device, pause it
|
|
||||||
if (!aVideo.element.paused) {
|
|
||||||
aVideo.element.pause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.stop(() => {
|
|
||||||
app.start(aStarted => {
|
|
||||||
if (!aStarted) {
|
|
||||||
dump("CastingApps: Unable to start app");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
app.remoteMedia(aRemoteMedia => {
|
|
||||||
if (!aRemoteMedia) {
|
|
||||||
dump("CastingApps: Failed to create remotemedia");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.session = {
|
|
||||||
service: aService,
|
|
||||||
app: app,
|
|
||||||
remoteMedia: aRemoteMedia,
|
|
||||||
data: {
|
|
||||||
title: aVideo.title,
|
|
||||||
source: aVideo.source,
|
|
||||||
poster: aVideo.poster,
|
|
||||||
},
|
|
||||||
videoRef: Cu.getWeakReference(aVideo.element),
|
|
||||||
};
|
|
||||||
}, this);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
filterFunc.bind(this)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
closeExternal: function() {
|
|
||||||
if (!this.session) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.session.remoteMedia.shutdown();
|
|
||||||
this._shutdown();
|
|
||||||
},
|
|
||||||
|
|
||||||
_shutdown: function() {
|
|
||||||
if (!this.session) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.session.app.stop();
|
|
||||||
let video = this.session.videoRef.get();
|
|
||||||
if (video) {
|
|
||||||
this._sendEventToVideo(video, { active: false });
|
|
||||||
this._updatePageAction();
|
|
||||||
}
|
|
||||||
|
|
||||||
delete this.session;
|
|
||||||
},
|
|
||||||
|
|
||||||
// RemoteMedia callback API methods
|
|
||||||
onRemoteMediaStart: function(aRemoteMedia) {
|
|
||||||
if (!this.session) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
aRemoteMedia.load(this.session.data);
|
|
||||||
GlobalEventDispatcher.sendRequest({
|
|
||||||
type: "Casting:Started",
|
|
||||||
device: this.session.service.friendlyName,
|
|
||||||
});
|
|
||||||
|
|
||||||
let video = this.session.videoRef.get();
|
|
||||||
if (video) {
|
|
||||||
this._sendEventToVideo(video, { active: true });
|
|
||||||
this._updatePageAction(video);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onRemoteMediaStop: function(aRemoteMedia) {
|
|
||||||
GlobalEventDispatcher.sendRequest({ type: "Casting:Stopped" });
|
|
||||||
this._shutdown();
|
|
||||||
},
|
|
||||||
|
|
||||||
onRemoteMediaStatus: function(aRemoteMedia) {
|
|
||||||
if (!this.session) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let status = aRemoteMedia.status;
|
|
||||||
switch (status) {
|
|
||||||
case "started":
|
|
||||||
GlobalEventDispatcher.sendRequest({ type: "Casting:Playing" });
|
|
||||||
break;
|
|
||||||
case "paused":
|
|
||||||
GlobalEventDispatcher.sendRequest({ type: "Casting:Paused" });
|
|
||||||
break;
|
|
||||||
case "completed":
|
|
||||||
this.closeExternal();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
var ConsoleAPI = {
|
|
||||||
observe: function observe(aMessage, aTopic, aData) {
|
|
||||||
aMessage = aMessage.wrappedJSObject;
|
|
||||||
|
|
||||||
let mappedArguments = Array.from(
|
|
||||||
aMessage.arguments,
|
|
||||||
this.formatResult,
|
|
||||||
this
|
|
||||||
);
|
|
||||||
let joinedArguments = mappedArguments.join(" ");
|
|
||||||
|
|
||||||
if (aMessage.level == "error" || aMessage.level == "warn") {
|
|
||||||
let flag =
|
|
||||||
aMessage.level == "error"
|
|
||||||
? Ci.nsIScriptError.errorFlag
|
|
||||||
: Ci.nsIScriptError.warningFlag;
|
|
||||||
let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(
|
|
||||||
Ci.nsIScriptError
|
|
||||||
);
|
|
||||||
consoleMsg.init(
|
|
||||||
joinedArguments,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
flag,
|
|
||||||
"content javascript"
|
|
||||||
);
|
|
||||||
Services.console.logMessage(consoleMsg);
|
|
||||||
} else if (aMessage.level == "trace") {
|
|
||||||
let bundle = Services.strings.createBundle(
|
|
||||||
"chrome://browser/locale/browser.properties"
|
|
||||||
);
|
|
||||||
let args = aMessage.arguments;
|
|
||||||
let filename = this.abbreviateSourceURL(args[0].filename);
|
|
||||||
let functionName =
|
|
||||||
args[0].functionName ||
|
|
||||||
bundle.GetStringFromName("stacktrace.anonymousFunction");
|
|
||||||
let lineNumber = args[0].lineNumber;
|
|
||||||
|
|
||||||
let body = bundle.formatStringFromName("stacktrace.outputMessage", [
|
|
||||||
filename,
|
|
||||||
functionName,
|
|
||||||
lineNumber,
|
|
||||||
]);
|
|
||||||
body += "\n";
|
|
||||||
args.forEach(function(aFrame) {
|
|
||||||
let functionName =
|
|
||||||
aFrame.functionName ||
|
|
||||||
bundle.GetStringFromName("stacktrace.anonymousFunction");
|
|
||||||
body +=
|
|
||||||
" " +
|
|
||||||
aFrame.filename +
|
|
||||||
" :: " +
|
|
||||||
functionName +
|
|
||||||
" :: " +
|
|
||||||
aFrame.lineNumber +
|
|
||||||
"\n";
|
|
||||||
});
|
|
||||||
|
|
||||||
Services.console.logStringMessage(body);
|
|
||||||
} else if (aMessage.level == "time" && aMessage.arguments) {
|
|
||||||
let bundle = Services.strings.createBundle(
|
|
||||||
"chrome://browser/locale/browser.properties"
|
|
||||||
);
|
|
||||||
let body = bundle.formatStringFromName("timer.start", [
|
|
||||||
aMessage.arguments.name,
|
|
||||||
]);
|
|
||||||
Services.console.logStringMessage(body);
|
|
||||||
} else if (aMessage.level == "timeEnd" && aMessage.arguments) {
|
|
||||||
let bundle = Services.strings.createBundle(
|
|
||||||
"chrome://browser/locale/browser.properties"
|
|
||||||
);
|
|
||||||
let body = bundle.formatStringFromName("timer.end", [
|
|
||||||
aMessage.arguments.name,
|
|
||||||
aMessage.arguments.duration,
|
|
||||||
]);
|
|
||||||
Services.console.logStringMessage(body);
|
|
||||||
} else if (
|
|
||||||
["group", "groupCollapsed", "groupEnd"].includes(aMessage.level)
|
|
||||||
) {
|
|
||||||
// Do nothing yet
|
|
||||||
} else {
|
|
||||||
Services.console.logStringMessage(joinedArguments);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getResultType: function getResultType(aResult) {
|
|
||||||
let type = aResult === null ? "null" : typeof aResult;
|
|
||||||
if (type == "object" && aResult.constructor && aResult.constructor.name) {
|
|
||||||
type = aResult.constructor.name;
|
|
||||||
}
|
|
||||||
return type.toLowerCase();
|
|
||||||
},
|
|
||||||
|
|
||||||
formatResult: function formatResult(aResult) {
|
|
||||||
let output = "";
|
|
||||||
let type = this.getResultType(aResult);
|
|
||||||
switch (type) {
|
|
||||||
case "string":
|
|
||||||
case "boolean":
|
|
||||||
case "date":
|
|
||||||
case "error":
|
|
||||||
case "number":
|
|
||||||
case "regexp":
|
|
||||||
output = aResult.toString();
|
|
||||||
break;
|
|
||||||
case "null":
|
|
||||||
case "undefined":
|
|
||||||
output = type;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
output = aResult.toString();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
},
|
|
||||||
|
|
||||||
abbreviateSourceURL: function abbreviateSourceURL(aSourceURL) {
|
|
||||||
// Remove any query parameters.
|
|
||||||
let hookIndex = aSourceURL.indexOf("?");
|
|
||||||
if (hookIndex > -1) {
|
|
||||||
aSourceURL = aSourceURL.substring(0, hookIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a trailing "/".
|
|
||||||
if (aSourceURL[aSourceURL.length - 1] == "/") {
|
|
||||||
aSourceURL = aSourceURL.substring(0, aSourceURL.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all but the last path component.
|
|
||||||
let slashIndex = aSourceURL.lastIndexOf("/");
|
|
||||||
if (slashIndex > -1) {
|
|
||||||
aSourceURL = aSourceURL.substring(slashIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return aSourceURL;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"ConsoleAPI",
|
|
||||||
"resource://gre/modules/Console.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Collection of methods and features specific to using a GeckoView instance.
|
|
||||||
* The code is isolated from browser.js for code size and performance reasons.
|
|
||||||
*/
|
|
||||||
var EmbedRT = {
|
|
||||||
_scopes: {},
|
|
||||||
|
|
||||||
onEvent: function(event, data, callback) {
|
|
||||||
switch (event) {
|
|
||||||
case "GeckoView:ImportScript":
|
|
||||||
this.importScript(data.scriptURL);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Loads a script file into a sandbox and calls an optional load function
|
|
||||||
*/
|
|
||||||
importScript: function(scriptURL) {
|
|
||||||
if (scriptURL in this._scopes) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let principal = Cc["@mozilla.org/systemprincipal;1"].createInstance(
|
|
||||||
Ci.nsIPrincipal
|
|
||||||
);
|
|
||||||
|
|
||||||
let sandbox = new Cu.Sandbox(principal, {
|
|
||||||
sandboxName: scriptURL,
|
|
||||||
wantGlobalProperties: ["indexedDB"],
|
|
||||||
});
|
|
||||||
|
|
||||||
sandbox.console = new ConsoleAPI({ consoleID: "script/" + scriptURL });
|
|
||||||
|
|
||||||
// As we don't want our caller to control the JS version used for the
|
|
||||||
// script file, we run loadSubScript within the context of the
|
|
||||||
// sandbox with the latest JS version set explicitly.
|
|
||||||
sandbox.__SCRIPT_URI_SPEC__ = scriptURL;
|
|
||||||
Cu.evalInSandbox(
|
|
||||||
"Components.classes['@mozilla.org/moz/jssubscript-loader;1'].createInstance(Components.interfaces.mozIJSSubScriptLoader).loadSubScript(__SCRIPT_URI_SPEC__);",
|
|
||||||
sandbox,
|
|
||||||
"ECMAv5"
|
|
||||||
);
|
|
||||||
|
|
||||||
this._scopes[scriptURL] = sandbox;
|
|
||||||
|
|
||||||
if ("load" in sandbox) {
|
|
||||||
let params = {
|
|
||||||
window: window,
|
|
||||||
resourceURI: scriptURL,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
sandbox.load(params);
|
|
||||||
} catch (e) {
|
|
||||||
dump(
|
|
||||||
"Exception calling 'load' method in script: " + scriptURL + "\n" + e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"ExtensionData",
|
|
||||||
"resource://gre/modules/Extension.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
var ExtensionPermissions = {
|
|
||||||
// id -> object containing update details (see applyUpdate() )
|
|
||||||
updates: new Map(),
|
|
||||||
|
|
||||||
// Prepare the strings needed for a permission notification.
|
|
||||||
_prepareStrings(info) {
|
|
||||||
let appName = Strings.brand.GetStringFromName("brandShortName");
|
|
||||||
let info2 = Object.assign({ appName }, info);
|
|
||||||
let strings = ExtensionData.formatPermissionStrings(info2, Strings.browser);
|
|
||||||
|
|
||||||
// We dump the main body of the dialog into a big android
|
|
||||||
// TextView. Build a big string with the full contents here.
|
|
||||||
let message = "";
|
|
||||||
if (strings.msgs.length > 0) {
|
|
||||||
message = [
|
|
||||||
strings.listIntro,
|
|
||||||
...strings.msgs.map(s => `\u2022 ${s}`),
|
|
||||||
].join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
header: strings.header.replace("<>", info.addonName),
|
|
||||||
message,
|
|
||||||
acceptText: strings.acceptText,
|
|
||||||
cancelText: strings.cancelText,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// Prepare an icon for a permission notification
|
|
||||||
_prepareIcon(iconURL) {
|
|
||||||
// We can render pngs with ResourceDrawableUtils
|
|
||||||
if (iconURL.endsWith(".png")) {
|
|
||||||
return iconURL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we can't render an icon, show the default
|
|
||||||
return "DEFAULT";
|
|
||||||
},
|
|
||||||
|
|
||||||
async observe(subject, topic, data) {
|
|
||||||
switch (topic) {
|
|
||||||
case "webextension-permission-prompt": {
|
|
||||||
let { target, info } = subject.wrappedJSObject;
|
|
||||||
let stringInfo = Object.assign({ addonName: info.addon.name }, info);
|
|
||||||
let details = this._prepareStrings(stringInfo);
|
|
||||||
details.icon = this._prepareIcon(info.icon);
|
|
||||||
details.type = "Extension:PermissionPrompt";
|
|
||||||
let accepted = await EventDispatcher.instance.sendRequestForResult(
|
|
||||||
details
|
|
||||||
);
|
|
||||||
|
|
||||||
if (accepted) {
|
|
||||||
info.resolve();
|
|
||||||
} else {
|
|
||||||
info.reject();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "webextension-update-permissions":
|
|
||||||
let info = subject.wrappedJSObject;
|
|
||||||
let { addon, resolve, reject } = info;
|
|
||||||
let stringInfo = Object.assign(
|
|
||||||
{
|
|
||||||
type: "update",
|
|
||||||
addonName: addon.name,
|
|
||||||
},
|
|
||||||
info
|
|
||||||
);
|
|
||||||
|
|
||||||
let details = this._prepareStrings(stringInfo);
|
|
||||||
|
|
||||||
// If there are no promptable permissions, just apply the update
|
|
||||||
if (details.message.length == 0) {
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store all the details about the update until the user chooses to
|
|
||||||
// look at update, at which point we will pick up in this.applyUpdate()
|
|
||||||
details.icon = this._prepareIcon(addon.iconURL || "dummy.svg");
|
|
||||||
|
|
||||||
let first = this.updates.size == 0;
|
|
||||||
this.updates.set(addon.id, { details, resolve, reject });
|
|
||||||
|
|
||||||
if (first) {
|
|
||||||
EventDispatcher.instance.sendRequest({
|
|
||||||
type: "Extension:ShowUpdateIcon",
|
|
||||||
value: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "webextension-optional-permission-prompt": {
|
|
||||||
let info = subject.wrappedJSObject;
|
|
||||||
let { name, resolve } = info;
|
|
||||||
let stringInfo = Object.assign(
|
|
||||||
{
|
|
||||||
type: "optional",
|
|
||||||
addonName: name,
|
|
||||||
},
|
|
||||||
info
|
|
||||||
);
|
|
||||||
|
|
||||||
let details = this._prepareStrings(stringInfo);
|
|
||||||
|
|
||||||
// If there are no promptable permissions, just apply the update
|
|
||||||
if (details.message.length == 0) {
|
|
||||||
resolve(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store all the details about the update until the user chooses to
|
|
||||||
// look at update, at which point we will pick up in this.applyUpdate()
|
|
||||||
details.icon = this._prepareIcon(info.icon || "dummy.svg");
|
|
||||||
|
|
||||||
details.type = "Extension:PermissionPrompt";
|
|
||||||
let accepted = await EventDispatcher.instance.sendRequestForResult(
|
|
||||||
details
|
|
||||||
);
|
|
||||||
resolve(accepted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async applyUpdate(id) {
|
|
||||||
if (!this.updates.has(id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let update = this.updates.get(id);
|
|
||||||
this.updates.delete(id);
|
|
||||||
if (this.updates.size == 0) {
|
|
||||||
EventDispatcher.instance.sendRequest({
|
|
||||||
type: "Extension:ShowUpdateIcon",
|
|
||||||
value: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let { details } = update;
|
|
||||||
details.type = "Extension:PermissionPrompt";
|
|
||||||
|
|
||||||
let accepted = await EventDispatcher.instance.sendRequestForResult(details);
|
|
||||||
if (accepted) {
|
|
||||||
update.resolve();
|
|
||||||
} else {
|
|
||||||
update.reject();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
var FeedHandler = {
|
|
||||||
PREF_CONTENTHANDLERS_BRANCH: "browser.contentHandlers.types.",
|
|
||||||
TYPE_MAYBE_FEED: "application/vnd.mozilla.maybe.feed",
|
|
||||||
|
|
||||||
_contentTypes: null,
|
|
||||||
|
|
||||||
getContentHandlers: function fh_getContentHandlers(contentType) {
|
|
||||||
if (!this._contentTypes) {
|
|
||||||
this.loadContentHandlers();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(contentType in this._contentTypes)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._contentTypes[contentType];
|
|
||||||
},
|
|
||||||
|
|
||||||
loadContentHandlers: function fh_loadContentHandlers() {
|
|
||||||
this._contentTypes = {};
|
|
||||||
|
|
||||||
let kids = Services.prefs
|
|
||||||
.getBranch(this.PREF_CONTENTHANDLERS_BRANCH)
|
|
||||||
.getChildList("");
|
|
||||||
|
|
||||||
// First get the numbers of the providers by getting all ###.uri prefs
|
|
||||||
let nums = [];
|
|
||||||
for (let i = 0; i < kids.length; i++) {
|
|
||||||
let match = /^(\d+)\.uri$/.exec(kids[i]);
|
|
||||||
if (!match) {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
nums.push(match[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort them, to get them back in order
|
|
||||||
nums.sort(function(a, b) {
|
|
||||||
return a - b;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Now register them
|
|
||||||
for (let i = 0; i < nums.length; i++) {
|
|
||||||
let branch = Services.prefs.getBranch(
|
|
||||||
this.PREF_CONTENTHANDLERS_BRANCH + nums[i] + "."
|
|
||||||
);
|
|
||||||
let vals = branch.getChildList("");
|
|
||||||
if (vals.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let type = branch.getCharPref("type");
|
|
||||||
let uri = branch.getComplexValue("uri", Ci.nsIPrefLocalizedString).data;
|
|
||||||
let title = branch.getComplexValue("title", Ci.nsIPrefLocalizedString)
|
|
||||||
.data;
|
|
||||||
|
|
||||||
if (!(type in this._contentTypes)) {
|
|
||||||
this._contentTypes[type] = [];
|
|
||||||
}
|
|
||||||
this._contentTypes[type].push({
|
|
||||||
contentType: type,
|
|
||||||
uri: uri,
|
|
||||||
name: title,
|
|
||||||
});
|
|
||||||
} catch (ex) {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onEvent: function fh_onEvent(event, args, callback) {
|
|
||||||
if (event === "Feeds:Subscribe") {
|
|
||||||
let tab = BrowserApp.getTabForId(args.tabId);
|
|
||||||
if (!tab) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let browser = tab.browser;
|
|
||||||
let feeds = browser.feeds;
|
|
||||||
if (feeds == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, let's decide on which feed to subscribe
|
|
||||||
let feedIndex = -1;
|
|
||||||
if (feeds.length > 1) {
|
|
||||||
let p = new Prompt({
|
|
||||||
window: browser.contentWindow,
|
|
||||||
title: Strings.browser.GetStringFromName("feedHandler.chooseFeed"),
|
|
||||||
})
|
|
||||||
.setSingleChoiceItems(
|
|
||||||
feeds.map(function(feed) {
|
|
||||||
return { label: feed.title || feed.href };
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.show(data => {
|
|
||||||
feedIndex = data.button;
|
|
||||||
if (feedIndex == -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loadFeed(feeds[feedIndex], browser);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loadFeed(feeds[0], browser);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
loadFeed: function fh_loadFeed(aFeed, aBrowser) {
|
|
||||||
let feedURL = aFeed.href;
|
|
||||||
|
|
||||||
// Next, we decide on which service to send the feed
|
|
||||||
let handlers = this.getContentHandlers(this.TYPE_MAYBE_FEED);
|
|
||||||
if (handlers.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON for Prompt
|
|
||||||
let p = new Prompt({
|
|
||||||
window: aBrowser.contentWindow,
|
|
||||||
title: Strings.browser.GetStringFromName("feedHandler.subscribeWith"),
|
|
||||||
})
|
|
||||||
.setSingleChoiceItems(
|
|
||||||
handlers.map(function(handler) {
|
|
||||||
return { label: handler.name };
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.show(function(data) {
|
|
||||||
if (data.button == -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge the handler URL and the feed URL
|
|
||||||
let readerURL = handlers[data.button].uri;
|
|
||||||
readerURL = readerURL.replace(/%s/gi, encodeURIComponent(feedURL));
|
|
||||||
|
|
||||||
// Open the resultant URL in a new tab
|
|
||||||
BrowserApp.addTab(readerURL, { parentId: BrowserApp.selectedTab.id });
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
var Feedback = {
|
|
||||||
get _feedbackURL() {
|
|
||||||
delete this._feedbackURL;
|
|
||||||
return (this._feedbackURL = Services.urlFormatter.formatURLPref(
|
|
||||||
"app.feedbackURL"
|
|
||||||
));
|
|
||||||
},
|
|
||||||
|
|
||||||
onEvent: function(event, data, callback) {
|
|
||||||
if (event !== "Feedback:Show") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't prompt for feedback in distribution builds.
|
|
||||||
try {
|
|
||||||
Services.prefs.getCharPref("distribution.id");
|
|
||||||
return;
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
let url = this._feedbackURL;
|
|
||||||
let browser = BrowserApp.selectOrAddTab(url, {
|
|
||||||
parentId: BrowserApp.selectedTab.id,
|
|
||||||
}).browser;
|
|
||||||
|
|
||||||
browser.addEventListener("FeedbackClose", this, false, true);
|
|
||||||
browser.addEventListener("FeedbackMaybeLater", this, false, true);
|
|
||||||
|
|
||||||
// Dispatch a custom event to the page content when feedback is prompted by the browser.
|
|
||||||
// This will be used by the page to determine it's being loaded directly by the browser,
|
|
||||||
// instead of by the user visiting the page, e.g. through browser history.
|
|
||||||
function loadListener(event) {
|
|
||||||
browser.removeEventListener("DOMContentLoaded", loadListener);
|
|
||||||
browser.contentDocument.dispatchEvent(
|
|
||||||
new CustomEvent("FeedbackPrompted")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
browser.addEventListener("DOMContentLoaded", loadListener);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEvent: function(event) {
|
|
||||||
if (!this._isAllowed(event.target)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.type) {
|
|
||||||
case "FeedbackClose":
|
|
||||||
// Do nothing.
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "FeedbackMaybeLater":
|
|
||||||
GlobalEventDispatcher.sendRequest({ type: "Feedback:MaybeLater" });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let win = event.target.ownerGlobal.top;
|
|
||||||
BrowserApp.closeTab(BrowserApp.getTabForWindow(win));
|
|
||||||
},
|
|
||||||
|
|
||||||
_isAllowed: function(node) {
|
|
||||||
let uri = node.ownerDocument.documentURIObject;
|
|
||||||
let feedbackURI = Services.io.newURI(this._feedbackURL);
|
|
||||||
return uri.prePath === feedbackURI.prePath;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,231 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
var FindHelper = {
|
|
||||||
_finder: null,
|
|
||||||
_targetTab: null,
|
|
||||||
_initialViewport: null,
|
|
||||||
_viewportChanged: false,
|
|
||||||
_result: null,
|
|
||||||
|
|
||||||
// Start of nsIObserver implementation.
|
|
||||||
|
|
||||||
onEvent: function(event, data, callback) {
|
|
||||||
switch (event) {
|
|
||||||
case "FindInPage:Opened": {
|
|
||||||
this._findOpened();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "FindInPage:Closed": {
|
|
||||||
this._uninit();
|
|
||||||
this._findClosed();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "Tab:Selected": {
|
|
||||||
// Allow for page switching.
|
|
||||||
this._uninit();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "FindInPage:Find": {
|
|
||||||
this.doFind(data.searchString);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "FindInPage:Next": {
|
|
||||||
this.findAgain(data.searchString, false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "FindInPage:Prev": {
|
|
||||||
this.findAgain(data.searchString, true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the FindInPageBar opens/ becomes visible, it's time to:
|
|
||||||
* 1. Add listeners for other message types sent from the FindInPageBar
|
|
||||||
* 2. initialize the Finder instance, if necessary.
|
|
||||||
*/
|
|
||||||
_findOpened: function() {
|
|
||||||
GlobalEventDispatcher.registerListener(this, [
|
|
||||||
"FindInPage:Find",
|
|
||||||
"FindInPage:Next",
|
|
||||||
"FindInPage:Prev",
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Initialize the finder component for the current page by performing a fake find.
|
|
||||||
this._init();
|
|
||||||
this._finder.requestMatchesCount("");
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch the Finder instance from the active tabs' browser and start tracking
|
|
||||||
* the active viewport.
|
|
||||||
*/
|
|
||||||
_init: function() {
|
|
||||||
// If there's no find in progress, start one.
|
|
||||||
if (this._finder) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._targetTab = BrowserApp.selectedTab;
|
|
||||||
try {
|
|
||||||
this._finder = this._targetTab.browser.finder;
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(
|
|
||||||
"FindHelper: " +
|
|
||||||
e +
|
|
||||||
"\n" +
|
|
||||||
"JS stack: \n" +
|
|
||||||
(e.stack || Components.stack.formattedStack)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._finder.addResultListener(this);
|
|
||||||
this._initialViewport = JSON.stringify(this._targetTab.getViewport());
|
|
||||||
this._viewportChanged = false;
|
|
||||||
|
|
||||||
WindowEventDispatcher.registerListener(this, ["Tab:Selected"]);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detach from the Finder instance (so stop listening for messages) and stop
|
|
||||||
* tracking the active viewport.
|
|
||||||
*/
|
|
||||||
_uninit: function() {
|
|
||||||
// If there's no find in progress, there's nothing to clean up.
|
|
||||||
if (!this._finder) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._finder.removeSelection();
|
|
||||||
this._finder.removeResultListener(this);
|
|
||||||
this._finder = null;
|
|
||||||
this._targetTab = null;
|
|
||||||
this._initialViewport = null;
|
|
||||||
this._viewportChanged = false;
|
|
||||||
|
|
||||||
WindowEventDispatcher.unregisterListener(this, ["Tab:Selected"]);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the FindInPageBar closes, it's time to stop listening for its messages.
|
|
||||||
*/
|
|
||||||
_findClosed: function() {
|
|
||||||
GlobalEventDispatcher.unregisterListener(this, [
|
|
||||||
"FindInPage:Find",
|
|
||||||
"FindInPage:Next",
|
|
||||||
"FindInPage:Prev",
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start an asynchronous find-in-page operation, using the current Finder
|
|
||||||
* instance and request to count the amount of matches.
|
|
||||||
* If no Finder instance is currently active, we'll lazily initialize it here.
|
|
||||||
*
|
|
||||||
* @param {String} searchString Word to search for in the current document
|
|
||||||
* @return {Object} Echo of the current find action
|
|
||||||
*/
|
|
||||||
doFind: function(searchString) {
|
|
||||||
if (!this._finder) {
|
|
||||||
this._init();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._finder.fastFind(searchString, false);
|
|
||||||
return { searchString, findBackwards: false };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restart the same find-in-page operation as before via `doFind()`. If we
|
|
||||||
* haven't called `doFind()`, we simply kick off a regular find.
|
|
||||||
*
|
|
||||||
* @param {String} searchString Word to search for in the current document
|
|
||||||
* @param {Boolean} findBackwards Direction to search in
|
|
||||||
* @return {Object} Echo of the current find action
|
|
||||||
*/
|
|
||||||
findAgain: function(searchString, findBackwards) {
|
|
||||||
// This always happens if the user taps next/previous after re-opening the
|
|
||||||
// search bar, and not only forces _init() but also an initial fastFind(STRING)
|
|
||||||
// before any findAgain(DIRECTION).
|
|
||||||
if (!this._finder) {
|
|
||||||
return this.doFind(searchString);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._finder.findAgain(searchString, findBackwards, false, false);
|
|
||||||
return { searchString, findBackwards };
|
|
||||||
},
|
|
||||||
|
|
||||||
// Start of Finder.jsm listener implementation.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pass along the count results to FindInPageBar for display. The result that
|
|
||||||
* is sent to the FindInPageBar is augmented with the current find-in-page count
|
|
||||||
* limit.
|
|
||||||
*
|
|
||||||
* @param {Object} result Result coming from the Finder instance that contains
|
|
||||||
* the following properties:
|
|
||||||
* - {Number} total The total amount of matches found
|
|
||||||
* - {Number} current The index of current found range
|
|
||||||
* in the document
|
|
||||||
*/
|
|
||||||
onMatchesCountResult: function(result) {
|
|
||||||
this._result = result;
|
|
||||||
|
|
||||||
GlobalEventDispatcher.sendRequest(
|
|
||||||
Object.assign(
|
|
||||||
{
|
|
||||||
type: "FindInPage:MatchesCountResult",
|
|
||||||
},
|
|
||||||
this._result
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When a find-in-page action finishes, this method is invoked. This is mainly
|
|
||||||
* used at the moment to detect if the current viewport has changed, which might
|
|
||||||
* be indicated by not finding a string in the current page.
|
|
||||||
*
|
|
||||||
* @param {Object} aData A dictionary, representing the find result, which
|
|
||||||
* contains the following properties:
|
|
||||||
* - {String} searchString Word that was searched for
|
|
||||||
* in the current document
|
|
||||||
* - {Number} result One of the following
|
|
||||||
* Ci.nsITypeAheadFind.* result
|
|
||||||
* indicators: FIND_FOUND,
|
|
||||||
* FIND_NOTFOUND, FIND_WRAPPED,
|
|
||||||
* FIND_PENDING
|
|
||||||
* - {Boolean} findBackwards Whether the search direction
|
|
||||||
* was backwards
|
|
||||||
* - {Boolean} findAgain Whether the previous search
|
|
||||||
* was repeated
|
|
||||||
* - {Boolean} drawOutline Whether we may (re-)draw the
|
|
||||||
* outline of a hyperlink
|
|
||||||
* - {Boolean} linksOnly Whether links-only mode was
|
|
||||||
* active
|
|
||||||
*/
|
|
||||||
onFindResult: function(aData) {
|
|
||||||
if (aData.result == Ci.nsITypeAheadFind.FIND_NOTFOUND) {
|
|
||||||
if (this._viewportChanged) {
|
|
||||||
if (this._targetTab != BrowserApp.selectedTab) {
|
|
||||||
// this should never happen
|
|
||||||
Cu.reportError("Warning: selected tab changed during find!");
|
|
||||||
// fall through and restore viewport on the initial tab anyway
|
|
||||||
}
|
|
||||||
this._targetTab.sendViewportUpdate();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Disabled until bug 1014113 is fixed
|
|
||||||
// ZoomHelper.zoomToRect(aData.rect);
|
|
||||||
this._viewportChanged = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
const LINKIFY_TIMEOUT = 0;
|
|
||||||
|
|
||||||
function Linkifier() {
|
|
||||||
this._linkifyTimer = null;
|
|
||||||
this._phoneRegex = /(?:\s|^)[\+]?(\(?\d{1,8}\)?)?([- ]+\(?\d{1,8}\)?)+( ?(x|ext) ?\d{1,3})?(?:\s|$)/g;
|
|
||||||
}
|
|
||||||
|
|
||||||
Linkifier.prototype = {
|
|
||||||
_buildAnchor: function(aDoc, aNumberText) {
|
|
||||||
let anchorNode = aDoc.createElement("a");
|
|
||||||
let cleanedText = "";
|
|
||||||
for (let i = 0; i < aNumberText.length; i++) {
|
|
||||||
let c = aNumberText.charAt(i);
|
|
||||||
if ((c >= "0" && c <= "9") || c == "+") {
|
|
||||||
// assuming there is only the leading '+'.
|
|
||||||
cleanedText += c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
anchorNode.setAttribute("href", "tel:" + cleanedText);
|
|
||||||
let nodeText = aDoc.createTextNode(aNumberText);
|
|
||||||
anchorNode.appendChild(nodeText);
|
|
||||||
return anchorNode;
|
|
||||||
},
|
|
||||||
|
|
||||||
_linkifyNodeNumbers: function(aNodeToProcess, aDoc) {
|
|
||||||
let parent = aNodeToProcess.parentNode;
|
|
||||||
let nodeText = aNodeToProcess.nodeValue;
|
|
||||||
|
|
||||||
// Replacing the original text node with a sequence of
|
|
||||||
// |text before number|anchor with number|text after number nodes.
|
|
||||||
// Each step a couple of (optional) text node and anchor node are appended.
|
|
||||||
let anchorNode = null;
|
|
||||||
let m = null;
|
|
||||||
let startIndex = 0;
|
|
||||||
let prevNode = null;
|
|
||||||
while ((m = this._phoneRegex.exec(nodeText))) {
|
|
||||||
anchorNode = this._buildAnchor(
|
|
||||||
aDoc,
|
|
||||||
nodeText.substr(m.index, m[0].length)
|
|
||||||
);
|
|
||||||
|
|
||||||
let textExistsBeforeNumber = m.index > startIndex;
|
|
||||||
let nodeToAdd = null;
|
|
||||||
if (textExistsBeforeNumber) {
|
|
||||||
nodeToAdd = aDoc.createTextNode(
|
|
||||||
nodeText.substr(startIndex, m.index - startIndex)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
nodeToAdd = anchorNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!prevNode) {
|
|
||||||
// first time, need to replace the whole node with the first new one.
|
|
||||||
parent.replaceChild(nodeToAdd, aNodeToProcess);
|
|
||||||
} else {
|
|
||||||
parent.insertBefore(nodeToAdd, prevNode.nextSibling);
|
|
||||||
} // inserts after.
|
|
||||||
|
|
||||||
if (textExistsBeforeNumber) {
|
|
||||||
// if we added the text node before the anchor, we still need to add the anchor node.
|
|
||||||
parent.insertBefore(anchorNode, nodeToAdd.nextSibling);
|
|
||||||
}
|
|
||||||
|
|
||||||
// next nodes need to be appended to this node.
|
|
||||||
prevNode = anchorNode;
|
|
||||||
startIndex = m.index + m[0].length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if some text is remaining after the last anchor.
|
|
||||||
if (startIndex > 0 && startIndex < nodeText.length) {
|
|
||||||
let lastNode = aDoc.createTextNode(nodeText.substr(startIndex));
|
|
||||||
parent.insertBefore(lastNode, prevNode.nextSibling);
|
|
||||||
return lastNode;
|
|
||||||
}
|
|
||||||
return anchorNode;
|
|
||||||
},
|
|
||||||
|
|
||||||
linkifyNumbers: function(aDoc) {
|
|
||||||
// Removing any installed timer in case the page has changed and a previous timer is still running.
|
|
||||||
if (this._linkifyTimer) {
|
|
||||||
clearTimeout(this._linkifyTimer);
|
|
||||||
this._linkifyTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let filterNode = function(node) {
|
|
||||||
if (
|
|
||||||
node.parentNode.tagName != "A" &&
|
|
||||||
node.parentNode.tagName != "SCRIPT" &&
|
|
||||||
node.parentNode.tagName != "NOSCRIPT" &&
|
|
||||||
node.parentNode.tagName != "STYLE" &&
|
|
||||||
node.parentNode.tagName != "APPLET" &&
|
|
||||||
node.parentNode.tagName != "TEXTAREA"
|
|
||||||
) {
|
|
||||||
return NodeFilter.FILTER_ACCEPT;
|
|
||||||
}
|
|
||||||
return NodeFilter.FILTER_REJECT;
|
|
||||||
};
|
|
||||||
|
|
||||||
let nodeWalker = aDoc.createTreeWalker(
|
|
||||||
aDoc.body,
|
|
||||||
NodeFilter.SHOW_TEXT,
|
|
||||||
filterNode,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
let parseNode = () => {
|
|
||||||
let node = nodeWalker.nextNode();
|
|
||||||
if (!node) {
|
|
||||||
this._linkifyTimer = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let lastAddedNode = this._linkifyNodeNumbers(node, aDoc);
|
|
||||||
// we assign a different timeout whether the node was processed or not.
|
|
||||||
if (lastAddedNode) {
|
|
||||||
nodeWalker.currentNode = lastAddedNode;
|
|
||||||
this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT);
|
|
||||||
} else {
|
|
||||||
this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this._linkifyTimer = setTimeout(parseNode, LINKIFY_TIMEOUT);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"Snackbars",
|
|
||||||
"resource://gre/modules/Snackbars.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
var MasterPassword = {
|
|
||||||
pref: "privacy.masterpassword.enabled",
|
|
||||||
|
|
||||||
get _pk11DB() {
|
|
||||||
delete this._pk11DB;
|
|
||||||
return (this._pk11DB = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
|
|
||||||
Ci.nsIPK11TokenDB
|
|
||||||
));
|
|
||||||
},
|
|
||||||
|
|
||||||
get enabled() {
|
|
||||||
let token = this._pk11DB.getInternalKeyToken();
|
|
||||||
if (token) {
|
|
||||||
return token.hasPassword;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
setPassword: function setPassword(aPassword) {
|
|
||||||
try {
|
|
||||||
let token = this._pk11DB.getInternalKeyToken();
|
|
||||||
if (token.needsUserInit) {
|
|
||||||
token.initPassword(aPassword);
|
|
||||||
} else if (!token.needsLogin()) {
|
|
||||||
token.changePassword("", aPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
dump("MasterPassword.setPassword: " + e);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
removePassword: function removePassword(aOldPassword) {
|
|
||||||
try {
|
|
||||||
let token = this._pk11DB.getInternalKeyToken();
|
|
||||||
if (token.checkPassword(aOldPassword)) {
|
|
||||||
token.changePassword(aOldPassword, "");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
dump("MasterPassword.removePassword: " + e + "\n");
|
|
||||||
}
|
|
||||||
Snackbars.show(
|
|
||||||
Strings.browser.GetStringFromName("masterPassword.incorrect"),
|
|
||||||
Snackbars.LENGTH_LONG
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
/* 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 MAX_CONTENT_VIEWERS_PREF = "browser.sessionhistory.max_total_viewers";
|
|
||||||
|
|
||||||
var MemoryObserver = {
|
|
||||||
// When we turn off the bfcache by overwriting the old default value, we want
|
|
||||||
// to be able to restore it later on if memory pressure decreases again.
|
|
||||||
_defaultMaxContentViewers: -1,
|
|
||||||
|
|
||||||
observe: function mo_observe(aSubject, aTopic, aData) {
|
|
||||||
if (aTopic == "memory-pressure") {
|
|
||||||
if (aData != "heap-minimize") {
|
|
||||||
this.handleLowMemory();
|
|
||||||
}
|
|
||||||
// The JS engine would normally GC on this notification, but since we
|
|
||||||
// disabled that in favor of this method (bug 669346), we should gc here.
|
|
||||||
// See bug 784040 for when this code was ported from XUL to native Fennec.
|
|
||||||
this.gc();
|
|
||||||
} else if (aTopic == "memory-pressure-stop") {
|
|
||||||
this.handleEnoughMemory();
|
|
||||||
} else if (aTopic == "Memory:Dump") {
|
|
||||||
this.dumpMemoryStats(aData);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleLowMemory: function() {
|
|
||||||
// do things to reduce memory usage here
|
|
||||||
if (
|
|
||||||
!Services.prefs.getBoolPref("browser.tabs.disableBackgroundZombification")
|
|
||||||
) {
|
|
||||||
let tabs = BrowserApp.tabs;
|
|
||||||
let selected = BrowserApp.selectedTab;
|
|
||||||
for (let i = 0; i < tabs.length; i++) {
|
|
||||||
if (tabs[i] != selected && !tabs[i].playingAudio) {
|
|
||||||
tabs[i].zombify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change some preferences temporarily for only this session
|
|
||||||
let defaults = Services.prefs.getDefaultBranch(null);
|
|
||||||
|
|
||||||
// Stop using the bfcache
|
|
||||||
if (
|
|
||||||
!Services.prefs.getBoolPref(
|
|
||||||
"browser.sessionhistory.bfcacheIgnoreMemoryPressure"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
this._defaultMaxContentViewers = defaults.getIntPref(
|
|
||||||
MAX_CONTENT_VIEWERS_PREF
|
|
||||||
);
|
|
||||||
defaults.setIntPref(MAX_CONTENT_VIEWERS_PREF, 0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEnoughMemory: function() {
|
|
||||||
// Re-enable the bfcache
|
|
||||||
let defaults = Services.prefs.getDefaultBranch(null);
|
|
||||||
if (
|
|
||||||
!Services.prefs.getBoolPref(
|
|
||||||
"browser.sessionhistory.bfcacheIgnoreMemoryPressure"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
defaults.setIntPref(
|
|
||||||
MAX_CONTENT_VIEWERS_PREF,
|
|
||||||
this._defaultMaxContentViewers
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
gc: function() {
|
|
||||||
window.windowUtils.garbageCollect();
|
|
||||||
Cu.forceGC();
|
|
||||||
},
|
|
||||||
|
|
||||||
dumpMemoryStats: function(aLabel) {
|
|
||||||
let memDumper = Cc["@mozilla.org/memory-info-dumper;1"].getService(
|
|
||||||
Ci.nsIMemoryInfoDumper
|
|
||||||
);
|
|
||||||
memDumper.dumpMemoryInfoToTempDir(
|
|
||||||
aLabel,
|
|
||||||
/* anonymize = */ false,
|
|
||||||
/* minimize = */ false
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
var OfflineApps = {
|
|
||||||
allowSite: function(aDocument) {
|
|
||||||
Services.perms.addFromPrincipal(
|
|
||||||
aDocument.nodePrincipal,
|
|
||||||
"offline-app",
|
|
||||||
Services.perms.ALLOW_ACTION
|
|
||||||
);
|
|
||||||
|
|
||||||
// When a site is enabled while loading, manifest resources will
|
|
||||||
// start fetching immediately. This one time we need to do it
|
|
||||||
// ourselves.
|
|
||||||
this._startFetching(aDocument);
|
|
||||||
},
|
|
||||||
|
|
||||||
disallowSite: function(aDocument) {
|
|
||||||
Services.perms.addFromPrincipal(
|
|
||||||
aDocument.nodePrincipal,
|
|
||||||
"offline-app",
|
|
||||||
Services.perms.DENY_ACTION
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
_startFetching: function(aDocument) {
|
|
||||||
if (!aDocument.documentElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let manifest = aDocument.documentElement.getAttribute("manifest");
|
|
||||||
if (!manifest) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let manifestURI = Services.io.newURI(
|
|
||||||
manifest,
|
|
||||||
aDocument.characterSet,
|
|
||||||
aDocument.documentURIObject
|
|
||||||
);
|
|
||||||
let updateService = Cc[
|
|
||||||
"@mozilla.org/offlinecacheupdate-service;1"
|
|
||||||
].getService(Ci.nsIOfflineCacheUpdateService);
|
|
||||||
updateService.scheduleUpdate(
|
|
||||||
manifestURI,
|
|
||||||
aDocument.documentURIObject,
|
|
||||||
aDocument.nodePrincipal,
|
|
||||||
window
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,204 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
var PermissionsHelper = {
|
|
||||||
_permissonTypes: [
|
|
||||||
"password",
|
|
||||||
"geolocation",
|
|
||||||
"popup",
|
|
||||||
"indexedDB",
|
|
||||||
"offline-app",
|
|
||||||
"desktop-notification",
|
|
||||||
"plugins",
|
|
||||||
"native-intent",
|
|
||||||
],
|
|
||||||
_permissionStrings: {
|
|
||||||
password: {
|
|
||||||
label: "password.logins",
|
|
||||||
allowed: "password.save",
|
|
||||||
denied: "password.dontSave",
|
|
||||||
},
|
|
||||||
geolocation: {
|
|
||||||
label: "geolocation.location",
|
|
||||||
allowed: "geolocation.allow",
|
|
||||||
denied: "geolocation.dontAllow",
|
|
||||||
},
|
|
||||||
popup: {
|
|
||||||
label: "blockPopups.label2",
|
|
||||||
allowed: "popup.show",
|
|
||||||
denied: "popup.dontShow",
|
|
||||||
},
|
|
||||||
indexedDB: {
|
|
||||||
label: "offlineApps.offlineData",
|
|
||||||
allowed: "offlineApps.allow",
|
|
||||||
denied: "offlineApps.dontAllow2",
|
|
||||||
},
|
|
||||||
"offline-app": {
|
|
||||||
label: "offlineApps.offlineData",
|
|
||||||
allowed: "offlineApps.allow",
|
|
||||||
denied: "offlineApps.dontAllow2",
|
|
||||||
},
|
|
||||||
"desktop-notification": {
|
|
||||||
label: "desktopNotification.notifications",
|
|
||||||
allowed: "desktopNotification2.allow",
|
|
||||||
denied: "desktopNotification2.dontAllow",
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
label: "clickToPlayPlugins.plugins",
|
|
||||||
allowed: "clickToPlayPlugins.activate",
|
|
||||||
denied: "clickToPlayPlugins.dontActivate",
|
|
||||||
},
|
|
||||||
"native-intent": {
|
|
||||||
label: "helperapps.openWithList2",
|
|
||||||
allowed: "helperapps.always",
|
|
||||||
denied: "helperapps.never",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
onEvent: function onEvent(event, data, callback) {
|
|
||||||
let principal = BrowserApp.selectedBrowser.contentPrincipal;
|
|
||||||
let check = false;
|
|
||||||
|
|
||||||
switch (event) {
|
|
||||||
case "Permissions:Check":
|
|
||||||
check = true;
|
|
||||||
// fall-through
|
|
||||||
|
|
||||||
case "Permissions:Get":
|
|
||||||
let permissions = [];
|
|
||||||
for (let i = 0; i < this._permissonTypes.length; i++) {
|
|
||||||
let type = this._permissonTypes[i];
|
|
||||||
let value = this.getPermission(principal, type);
|
|
||||||
|
|
||||||
// Only add the permission if it was set by the user
|
|
||||||
if (value == Services.perms.UNKNOWN_ACTION) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (check) {
|
|
||||||
GlobalEventDispatcher.sendRequest({
|
|
||||||
type: "Permissions:CheckResult",
|
|
||||||
hasPermissions: true,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Get the strings that correspond to the permission type
|
|
||||||
let typeStrings = this._permissionStrings[type];
|
|
||||||
let label = Strings.browser.GetStringFromName(typeStrings.label);
|
|
||||||
|
|
||||||
// Get the key to look up the appropriate string entity
|
|
||||||
let valueKey =
|
|
||||||
value == Services.perms.ALLOW_ACTION ? "allowed" : "denied";
|
|
||||||
let valueString = Strings.browser.GetStringFromName(
|
|
||||||
typeStrings[valueKey]
|
|
||||||
);
|
|
||||||
|
|
||||||
permissions.push({
|
|
||||||
type: type,
|
|
||||||
setting: label,
|
|
||||||
value: valueString,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (check) {
|
|
||||||
GlobalEventDispatcher.sendRequest({
|
|
||||||
type: "Permissions:CheckResult",
|
|
||||||
hasPermissions: false,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep track of permissions, so we know which ones to clear
|
|
||||||
this._currentPermissions = permissions;
|
|
||||||
|
|
||||||
WindowEventDispatcher.sendRequest({
|
|
||||||
type: "Permissions:Data",
|
|
||||||
permissions: permissions,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "Permissions:Clear":
|
|
||||||
// An array of the indices of the permissions we want to clear
|
|
||||||
let permissionsToClear = data.permissions;
|
|
||||||
let privacyContext = BrowserApp.selectedBrowser.docShell.QueryInterface(
|
|
||||||
Ci.nsILoadContext
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let i = 0; i < permissionsToClear.length; i++) {
|
|
||||||
let indexToClear = permissionsToClear[i];
|
|
||||||
let permissionType = this._currentPermissions[indexToClear].type;
|
|
||||||
this.clearPermission(uri, permissionType, privacyContext);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the permission value stored for a specified permission type.
|
|
||||||
*
|
|
||||||
* @param aType
|
|
||||||
* The permission type string stored in permission manager.
|
|
||||||
* e.g. "geolocation", "indexedDB", "popup"
|
|
||||||
*
|
|
||||||
* @return A permission value defined in nsIPermissionManager.
|
|
||||||
*/
|
|
||||||
getPermission: function getPermission(aPrincipal, aType) {
|
|
||||||
let aURI = BrowserApp.selectedBrowser.lastURI;
|
|
||||||
// Password saving isn't a nsIPermissionManager permission type, so handle
|
|
||||||
// it seperately.
|
|
||||||
if (aType == "password") {
|
|
||||||
// By default, login saving is enabled, so if it is disabled, the
|
|
||||||
// user selected the never remember option
|
|
||||||
if (!Services.logins.getLoginSavingEnabled(aURI.displayPrePath)) {
|
|
||||||
return Services.perms.DENY_ACTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to see if the user ever actually saved a login
|
|
||||||
if (Services.logins.countLogins(aURI.displayPrePath, "", "")) {
|
|
||||||
return Services.perms.ALLOW_ACTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Services.perms.UNKNOWN_ACTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Geolocation consumers use testExactPermissionForPrincipal
|
|
||||||
if (aType == "geolocation") {
|
|
||||||
return Services.perms.testExactPermissionFromPrincipal(aPrincipal, aType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Services.perms.testPermissionFromPrincipal(aPrincipal, aType);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears a user-set permission value for the site given a permission type.
|
|
||||||
*
|
|
||||||
* @param aType
|
|
||||||
* The permission type string stored in permission manager.
|
|
||||||
* e.g. "geolocation", "indexedDB", "popup"
|
|
||||||
*/
|
|
||||||
clearPermission: function clearPermission(aPrincipal, aType, aContext) {
|
|
||||||
// Password saving isn't a nsIPermissionManager permission type, so handle
|
|
||||||
// it seperately.
|
|
||||||
if (aType == "password") {
|
|
||||||
// Get rid of exisiting stored logings
|
|
||||||
let logins = Services.logins.findLogins(aURI.displayPrePath, "", "");
|
|
||||||
for (let i = 0; i < logins.length; i++) {
|
|
||||||
Services.logins.removeLogin(logins[i]);
|
|
||||||
}
|
|
||||||
// Re-set login saving to enabled
|
|
||||||
Services.logins.setLoginSavingEnabled(aURI.displayPrePath, true);
|
|
||||||
} else {
|
|
||||||
Services.perms.removeFromPrincipal(aPrincipal, aType);
|
|
||||||
// Clear content prefs set in ContentPermissionPrompt.js
|
|
||||||
Cc["@mozilla.org/content-pref/service;1"]
|
|
||||||
.getService(Ci.nsIContentPrefService2)
|
|
||||||
.removeByDomainAndName(
|
|
||||||
aURI.spec,
|
|
||||||
aType + ".request.remember",
|
|
||||||
aContext
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
/* -*- Mode: tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
||||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
||||||
/* 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 TOPIC_PRESENTATION_VIEW_READY = "presentation-view-ready";
|
|
||||||
const TOPIC_PRESENTATION_RECEIVER_LAUNCH = "presentation-receiver:launch";
|
|
||||||
const TOPIC_PRESENTATION_RECEIVER_LAUNCH_RESPONSE =
|
|
||||||
"presentation-receiver:launch:response";
|
|
||||||
|
|
||||||
// globals Services
|
|
||||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
|
|
||||||
function log(str) {
|
|
||||||
// dump("-*- PresentationView.js -*-: " + str + "\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
let PresentationView = {
|
|
||||||
_id: null,
|
|
||||||
|
|
||||||
startup: function startup() {
|
|
||||||
// use hash as the ID of this top level window
|
|
||||||
this._id = window.location.hash.substr(1);
|
|
||||||
|
|
||||||
// Listen "presentation-receiver:launch" sent from
|
|
||||||
// PresentationRequestUIGlue.
|
|
||||||
Services.obs.addObserver(this, TOPIC_PRESENTATION_RECEIVER_LAUNCH);
|
|
||||||
|
|
||||||
// Notify PresentationView is ready.
|
|
||||||
Services.obs.notifyObservers(null, TOPIC_PRESENTATION_VIEW_READY, this._id);
|
|
||||||
},
|
|
||||||
|
|
||||||
stop: function stop() {
|
|
||||||
Services.obs.removeObserver(this, TOPIC_PRESENTATION_RECEIVER_LAUNCH);
|
|
||||||
},
|
|
||||||
|
|
||||||
observe: function observe(aSubject, aTopic, aData) {
|
|
||||||
log("Got observe: aTopic=" + aTopic);
|
|
||||||
|
|
||||||
let requestData = JSON.parse(aData);
|
|
||||||
if (this._id != requestData.windowId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let browser = document.getElementById("content");
|
|
||||||
browser.setAttribute("mozpresentation", requestData.url);
|
|
||||||
try {
|
|
||||||
browser.loadURI(requestData.url);
|
|
||||||
Services.obs.notifyObservers(
|
|
||||||
browser,
|
|
||||||
TOPIC_PRESENTATION_RECEIVER_LAUNCH_RESPONSE,
|
|
||||||
JSON.stringify({ result: "success", requestId: requestData.requestId })
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
Services.obs.notifyObservers(
|
|
||||||
null,
|
|
||||||
TOPIC_PRESENTATION_RECEIVER_LAUNCH_RESPONSE,
|
|
||||||
JSON.stringify({ result: "error", reason: e.message })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<!-- 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/. -->
|
|
||||||
|
|
||||||
<window id="presentation-window"
|
|
||||||
onload="PresentationView.startup();"
|
|
||||||
onunload="PresentationView.stop();"
|
|
||||||
windowtype="navigator:browser"
|
|
||||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
|
||||||
|
|
||||||
<browser id="content" type="content" src="about:blank" flex="1"/>
|
|
||||||
|
|
||||||
<script type="application/javascript" src="chrome://browser/content/PresentationView.js"/>
|
|
||||||
</window>
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
|
||||||
/* 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";
|
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"Snackbars",
|
|
||||||
"resource://gre/modules/Snackbars.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
var PrintHelper = {
|
|
||||||
onEvent: function(event, data, callback) {
|
|
||||||
let browser = BrowserApp.selectedBrowser;
|
|
||||||
|
|
||||||
switch (event) {
|
|
||||||
case "Print:PDF":
|
|
||||||
this.generatePDF(browser).then(
|
|
||||||
data => callback.onSuccess(data),
|
|
||||||
error => callback.onError(error)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
generatePDF: function(aBrowser) {
|
|
||||||
// Create the final destination file location
|
|
||||||
let fileName = ContentAreaUtils.getDefaultFileName(
|
|
||||||
aBrowser.contentTitle,
|
|
||||||
aBrowser.currentURI,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
fileName = fileName.trim() + ".pdf";
|
|
||||||
|
|
||||||
let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
|
||||||
file.append(fileName);
|
|
||||||
file.createUnique(file.NORMAL_FILE_TYPE, parseInt("666", 8));
|
|
||||||
|
|
||||||
let printSettings = Cc[
|
|
||||||
"@mozilla.org/gfx/printsettings-service;1"
|
|
||||||
].getService(Ci.nsIPrintSettingsService).newPrintSettings;
|
|
||||||
printSettings.printSilent = true;
|
|
||||||
printSettings.showPrintProgress = false;
|
|
||||||
printSettings.printBGImages = false;
|
|
||||||
printSettings.printBGColors = false;
|
|
||||||
printSettings.printToFile = true;
|
|
||||||
printSettings.toFileName = file.path;
|
|
||||||
printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
|
|
||||||
|
|
||||||
let webBrowserPrint = aBrowser.contentWindow.getInterface(
|
|
||||||
Ci.nsIWebBrowserPrint
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
webBrowserPrint.print(printSettings, {
|
|
||||||
onStateChange: function(webProgress, request, stateFlags, status) {
|
|
||||||
// We get two STATE_START calls, one for STATE_IS_DOCUMENT and one for STATE_IS_NETWORK
|
|
||||||
if (
|
|
||||||
stateFlags & Ci.nsIWebProgressListener.STATE_START &&
|
|
||||||
stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK
|
|
||||||
) {
|
|
||||||
// Let the user know something is happening. Generating the PDF can take some time.
|
|
||||||
Snackbars.show(
|
|
||||||
Strings.browser.GetStringFromName("alertPrintjobToast"),
|
|
||||||
Snackbars.LENGTH_LONG
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We get two STATE_STOP calls, one for STATE_IS_DOCUMENT and one for STATE_IS_NETWORK
|
|
||||||
if (
|
|
||||||
stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
|
||||||
stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK
|
|
||||||
) {
|
|
||||||
if (Components.isSuccessCode(status)) {
|
|
||||||
// Send the details to Java
|
|
||||||
resolve({ file: file.path, title: fileName });
|
|
||||||
} else {
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onProgressChange: function() {},
|
|
||||||
onLocationChange: function() {},
|
|
||||||
onStatusChange: function() {},
|
|
||||||
onSecurityChange: function() {},
|
|
||||||
onContentBlockingEvent: function() {},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,342 +0,0 @@
|
||||||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
|
||||||
/* 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";
|
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"Snackbars",
|
|
||||||
"resource://gre/modules/Snackbars.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
/* globals MAX_URI_LENGTH, MAX_TITLE_LENGTH */
|
|
||||||
|
|
||||||
var Reader = {
|
|
||||||
// These values should match those defined in BrowserContract.java.
|
|
||||||
STATUS_UNFETCHED: 0,
|
|
||||||
STATUS_FETCH_FAILED_TEMPORARY: 1,
|
|
||||||
STATUS_FETCH_FAILED_PERMANENT: 2,
|
|
||||||
STATUS_FETCH_FAILED_UNSUPPORTED_FORMAT: 3,
|
|
||||||
STATUS_FETCHED_ARTICLE: 4,
|
|
||||||
|
|
||||||
get _hasUsedToolbar() {
|
|
||||||
delete this._hasUsedToolbar;
|
|
||||||
return (this._hasUsedToolbar = Services.prefs.getBoolPref(
|
|
||||||
"reader.has_used_toolbar"
|
|
||||||
));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* BackPressListener (listeners / ReaderView Ids).
|
|
||||||
*/
|
|
||||||
_backPressListeners: [],
|
|
||||||
_backPressViewIds: [],
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a backPressListener for this tabId / ReaderView Id pair.
|
|
||||||
*/
|
|
||||||
_addBackPressListener: function(tabId, viewId, listener) {
|
|
||||||
this._backPressListeners[tabId] = listener;
|
|
||||||
this._backPressViewIds[viewId] = tabId;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a backPressListener for this ReaderView Id.
|
|
||||||
*/
|
|
||||||
_removeBackPressListener: function(viewId) {
|
|
||||||
let tabId = this._backPressViewIds[viewId];
|
|
||||||
if (tabId != undefined) {
|
|
||||||
this._backPressListeners[tabId] = null;
|
|
||||||
delete this._backPressViewIds[viewId];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the requested tab has a backPress listener, return its results, else false.
|
|
||||||
*/
|
|
||||||
onBackPress: function(tabId) {
|
|
||||||
let listener = this._backPressListeners[tabId];
|
|
||||||
return { handled: listener ? listener() : false };
|
|
||||||
},
|
|
||||||
|
|
||||||
onEvent: function Reader_onEvent(event, data, callback) {
|
|
||||||
switch (event) {
|
|
||||||
case "Reader:RemoveFromCache": {
|
|
||||||
ReaderMode.removeArticleFromCache(data.url).catch(e =>
|
|
||||||
Cu.reportError("Error removing article from cache: " + e)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "Reader:AddToCache": {
|
|
||||||
let tab = BrowserApp.getTabForId(data.tabID);
|
|
||||||
if (!tab) {
|
|
||||||
throw new Error(
|
|
||||||
"No tab for tabID = " +
|
|
||||||
data.tabID +
|
|
||||||
" when trying to save reader view article"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the article is coming from reader mode, we must have fetched it already.
|
|
||||||
this._getArticleData(tab.browser)
|
|
||||||
.then(article => {
|
|
||||||
ReaderMode.storeArticleInCache(article);
|
|
||||||
})
|
|
||||||
.catch(e => Cu.reportError("Error storing article in cache: " + e));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
receiveMessage: function(message) {
|
|
||||||
switch (message.name) {
|
|
||||||
case "Reader:ArticleGet":
|
|
||||||
this._getArticle(message.data.url).then(
|
|
||||||
article => {
|
|
||||||
// Make sure the target browser is still alive before trying to send data back.
|
|
||||||
if (message.target.messageManager) {
|
|
||||||
message.target.messageManager.sendAsyncMessage(
|
|
||||||
"Reader:ArticleData",
|
|
||||||
{ article: article }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
e => {
|
|
||||||
if (e && e.newURL) {
|
|
||||||
message.target.loadURI(
|
|
||||||
"about:reader?url=" + encodeURIComponent(e.newURL)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// On DropdownClosed in ReaderView, we cleanup / clear existing BackPressListener.
|
|
||||||
case "Reader:DropdownClosed": {
|
|
||||||
this._removeBackPressListener(message.data);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// On DropdownOpened in ReaderView, we add BackPressListener to handle a subsequent BACK request.
|
|
||||||
case "Reader:DropdownOpened": {
|
|
||||||
let tabId = BrowserApp.selectedTab.id;
|
|
||||||
this._addBackPressListener(tabId, message.data, () => {
|
|
||||||
// User hit BACK key while ReaderView has the banner font-dropdown opened.
|
|
||||||
// Close it and return prevent-default.
|
|
||||||
if (message.target.messageManager) {
|
|
||||||
message.target.messageManager.sendAsyncMessage(
|
|
||||||
"Reader:CloseDropdown"
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// We can assume ReaderView banner's font-dropdown doesn't need to be closed.
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "Reader:FaviconRequest": {
|
|
||||||
GlobalEventDispatcher.sendRequestForResult({
|
|
||||||
type: "Reader:FaviconRequest",
|
|
||||||
url: message.data.url,
|
|
||||||
}).then(data => {
|
|
||||||
message.target.messageManager.sendAsyncMessage(
|
|
||||||
"Reader:FaviconReturn",
|
|
||||||
data
|
|
||||||
);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "Reader:SystemUIVisibility":
|
|
||||||
this._showSystemUI(message.data.visible);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "Reader:ToolbarHidden":
|
|
||||||
if (!this._hasUsedToolbar) {
|
|
||||||
Snackbars.show(
|
|
||||||
Strings.browser.GetStringFromName("readerMode.toolbarTip"),
|
|
||||||
Snackbars.LENGTH_LONG
|
|
||||||
);
|
|
||||||
Services.prefs.setBoolPref("reader.has_used_toolbar", true);
|
|
||||||
this._hasUsedToolbar = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "Reader:UpdateReaderButton": {
|
|
||||||
let tab = BrowserApp.getTabForBrowser(message.target);
|
|
||||||
tab.browser.isArticle = message.data.isArticle;
|
|
||||||
this.updatePageAction(tab);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
pageAction: {
|
|
||||||
readerModeCallback: function(browser) {
|
|
||||||
let url = browser.currentURI.spec;
|
|
||||||
if (url.startsWith("about:reader")) {
|
|
||||||
UITelemetry.addEvent("action.1", "button", null, "reader_exit");
|
|
||||||
} else {
|
|
||||||
UITelemetry.addEvent("action.1", "button", null, "reader_enter");
|
|
||||||
}
|
|
||||||
browser.messageManager.sendAsyncMessage("Reader:ToggleReaderMode");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
updatePageAction: function(tab) {
|
|
||||||
if (!tab.getActive()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.pageAction.id) {
|
|
||||||
PageActions.remove(this.pageAction.id);
|
|
||||||
delete this.pageAction.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
let showPageAction = (icon, title, useTint) => {
|
|
||||||
this.pageAction.id = PageActions.add({
|
|
||||||
icon: icon,
|
|
||||||
title: title,
|
|
||||||
clickCallback: () => this.pageAction.readerModeCallback(browser),
|
|
||||||
important: true,
|
|
||||||
useTint: useTint,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
let browser = tab.browser;
|
|
||||||
if (browser.currentURI.spec.startsWith("about:reader")) {
|
|
||||||
showPageAction(
|
|
||||||
"drawable://ic_readermode_on",
|
|
||||||
Strings.reader.GetStringFromName("readerView.close"),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
// Only start a reader session if the viewer is in the foreground. We do
|
|
||||||
// not track background reader viewers.
|
|
||||||
UITelemetry.startSession("reader.1", null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// not in ReaderMode, to make sure System UI is visible, not dimmed.
|
|
||||||
this._showSystemUI(true);
|
|
||||||
|
|
||||||
// Only stop a reader session if the foreground viewer is not visible.
|
|
||||||
UITelemetry.stopSession("reader.1", "", null);
|
|
||||||
|
|
||||||
if (browser.isArticle) {
|
|
||||||
showPageAction(
|
|
||||||
"drawable://ic_readermode",
|
|
||||||
Strings.reader.GetStringFromName("readerView.enter"),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
UITelemetry.addEvent("show.1", "button", null, "reader_available");
|
|
||||||
this._sendMmaEvent("reader_available");
|
|
||||||
} else {
|
|
||||||
UITelemetry.addEvent("show.1", "button", null, "reader_unavailable");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_sendMmaEvent: function(event) {
|
|
||||||
WindowEventDispatcher.sendRequest({
|
|
||||||
type: "Mma:" + event,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_showSystemUI: function(visibility) {
|
|
||||||
WindowEventDispatcher.sendRequest({
|
|
||||||
type: "SystemUI:Visibility",
|
|
||||||
visible: visibility,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an article for a given URL. This method will download and parse a document
|
|
||||||
* if it does not find the article in the cache.
|
|
||||||
*
|
|
||||||
* @param url The article URL.
|
|
||||||
* @return {Promise}
|
|
||||||
* @resolves JS object representing the article, or null if no article is found.
|
|
||||||
*/
|
|
||||||
async _getArticle(url) {
|
|
||||||
// First try to find a parsed article in the cache.
|
|
||||||
let article = await ReaderMode.getArticleFromCache(url);
|
|
||||||
if (article) {
|
|
||||||
return article;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Article hasn't been found in the cache, we need to
|
|
||||||
// download the page and parse the article out of it.
|
|
||||||
return ReaderMode.downloadAndParseDocument(url).catch(e => {
|
|
||||||
if (e && e.newURL) {
|
|
||||||
// Pass up the error so we can navigate the browser in question to the new URL:
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
Cu.reportError("Error downloading and parsing document: " + e);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_getArticleData: function(browser) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (browser == null) {
|
|
||||||
reject("_getArticleData needs valid browser");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mm = browser.messageManager;
|
|
||||||
let listener = message => {
|
|
||||||
mm.removeMessageListener("Reader:StoredArticleData", listener);
|
|
||||||
resolve(message.data.article);
|
|
||||||
};
|
|
||||||
mm.addMessageListener("Reader:StoredArticleData", listener);
|
|
||||||
mm.sendAsyncMessage("Reader:GetStoredArticleData");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrates old indexedDB reader mode cache to new JSON cache.
|
|
||||||
*/
|
|
||||||
async migrateCache() {
|
|
||||||
let cacheDB = await new Promise((resolve, reject) => {
|
|
||||||
let request = window.indexedDB.open("about:reader", 1);
|
|
||||||
request.onsuccess = event => resolve(event.target.result);
|
|
||||||
request.onerror = event => reject(request.error);
|
|
||||||
|
|
||||||
// If there is no DB to migrate, don't do anything.
|
|
||||||
request.onupgradeneeded = event => resolve(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!cacheDB) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let articles = await new Promise((resolve, reject) => {
|
|
||||||
let articles = [];
|
|
||||||
|
|
||||||
let transaction = cacheDB.transaction(cacheDB.objectStoreNames);
|
|
||||||
let store = transaction.objectStore(cacheDB.objectStoreNames[0]);
|
|
||||||
|
|
||||||
let request = store.openCursor();
|
|
||||||
request.onsuccess = event => {
|
|
||||||
let cursor = event.target.result;
|
|
||||||
if (!cursor) {
|
|
||||||
resolve(articles);
|
|
||||||
} else {
|
|
||||||
articles.push(cursor.value);
|
|
||||||
cursor.continue();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
request.onerror = event => reject(request.error);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let article of articles) {
|
|
||||||
await ReaderMode.storeArticleInCache(article);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the database.
|
|
||||||
window.indexedDB.deleteDatabase("about:reader");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,406 +0,0 @@
|
||||||
// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
|
||||||
/* 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/. */
|
|
||||||
/* globals DebuggerServer */
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "require", () => {
|
|
||||||
let { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
|
|
||||||
return require;
|
|
||||||
});
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "DebuggerServer", () => {
|
|
||||||
let { DebuggerServer } = require("devtools/server/debugger-server");
|
|
||||||
return DebuggerServer;
|
|
||||||
});
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "SocketListener", () => {
|
|
||||||
let { SocketListener } = require("devtools/shared/security/socket");
|
|
||||||
return SocketListener;
|
|
||||||
});
|
|
||||||
|
|
||||||
var RemoteDebugger = {
|
|
||||||
init(aWindow) {
|
|
||||||
this._windowType = "navigator:browser";
|
|
||||||
|
|
||||||
USBRemoteDebugger.init();
|
|
||||||
WiFiRemoteDebugger.init();
|
|
||||||
|
|
||||||
const listener = event => {
|
|
||||||
if (event.target !== aWindow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newType =
|
|
||||||
event.type === "activate" ? "navigator:browser" : "navigator:geckoview";
|
|
||||||
if (this._windowType === newType) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._windowType = newType;
|
|
||||||
if (this.isAnyEnabled) {
|
|
||||||
this.initServer();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
aWindow.addEventListener("activate", listener, { mozSystemGroup: true });
|
|
||||||
aWindow.addEventListener("deactivate", listener, { mozSystemGroup: true });
|
|
||||||
},
|
|
||||||
|
|
||||||
get isAnyEnabled() {
|
|
||||||
return USBRemoteDebugger.isEnabled || WiFiRemoteDebugger.isEnabled;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompt the user to accept or decline the incoming connection.
|
|
||||||
*
|
|
||||||
* @param session object
|
|
||||||
* The session object will contain at least the following fields:
|
|
||||||
* {
|
|
||||||
* authentication,
|
|
||||||
* client: {
|
|
||||||
* host,
|
|
||||||
* port
|
|
||||||
* },
|
|
||||||
* server: {
|
|
||||||
* host,
|
|
||||||
* port
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* Specific authentication modes may include additional fields. Check
|
|
||||||
* the different |allowConnection| methods in
|
|
||||||
* devtools/shared/security/auth.js.
|
|
||||||
* @return An AuthenticationResult value.
|
|
||||||
* A promise that will be resolved to the above is also allowed.
|
|
||||||
*/
|
|
||||||
allowConnection(session) {
|
|
||||||
if (this._promptingForAllow) {
|
|
||||||
// Don't stack connection prompts if one is already open
|
|
||||||
return DebuggerServer.AuthenticationResult.DENY;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!session.server.port) {
|
|
||||||
this._promptingForAllow = this._promptForUSB(session);
|
|
||||||
} else {
|
|
||||||
this._promptingForAllow = this._promptForTCP(session);
|
|
||||||
}
|
|
||||||
this._promptingForAllow.then(() => (this._promptingForAllow = null));
|
|
||||||
|
|
||||||
return this._promptingForAllow;
|
|
||||||
},
|
|
||||||
|
|
||||||
_promptForUSB(session) {
|
|
||||||
if (session.authentication !== "PROMPT") {
|
|
||||||
// This dialog is not prepared for any other authentication method at
|
|
||||||
// this time.
|
|
||||||
return DebuggerServer.AuthenticationResult.DENY;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
|
||||||
let title = Strings.browser.GetStringFromName(
|
|
||||||
"remoteIncomingPromptTitle"
|
|
||||||
);
|
|
||||||
let msg = Strings.browser.GetStringFromName("remoteIncomingPromptUSB");
|
|
||||||
let allow = Strings.browser.GetStringFromName(
|
|
||||||
"remoteIncomingPromptAllow"
|
|
||||||
);
|
|
||||||
let deny = Strings.browser.GetStringFromName("remoteIncomingPromptDeny");
|
|
||||||
|
|
||||||
// Make prompt. Note: button order is in reverse.
|
|
||||||
let prompt = new Prompt({
|
|
||||||
window: null,
|
|
||||||
hint: "remotedebug",
|
|
||||||
title: title,
|
|
||||||
message: msg,
|
|
||||||
buttons: [allow, deny],
|
|
||||||
priority: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
prompt.show(data => {
|
|
||||||
let result = data.button;
|
|
||||||
if (result === 0) {
|
|
||||||
resolve(DebuggerServer.AuthenticationResult.ALLOW);
|
|
||||||
} else {
|
|
||||||
resolve(DebuggerServer.AuthenticationResult.DENY);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_promptForTCP(session) {
|
|
||||||
if (session.authentication !== "OOB_CERT" || !session.client.cert) {
|
|
||||||
// This dialog is not prepared for any other authentication method at
|
|
||||||
// this time.
|
|
||||||
return DebuggerServer.AuthenticationResult.DENY;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
|
||||||
let title = Strings.browser.GetStringFromName(
|
|
||||||
"remoteIncomingPromptTitle"
|
|
||||||
);
|
|
||||||
let msg = Strings.browser.formatStringFromName(
|
|
||||||
"remoteIncomingPromptTCP",
|
|
||||||
[session.client.host, session.client.port]
|
|
||||||
);
|
|
||||||
let scan = Strings.browser.GetStringFromName("remoteIncomingPromptScan");
|
|
||||||
let scanAndRemember = Strings.browser.GetStringFromName(
|
|
||||||
"remoteIncomingPromptScanAndRemember"
|
|
||||||
);
|
|
||||||
let deny = Strings.browser.GetStringFromName("remoteIncomingPromptDeny");
|
|
||||||
|
|
||||||
// Make prompt. Note: button order is in reverse.
|
|
||||||
let prompt = new Prompt({
|
|
||||||
window: null,
|
|
||||||
hint: "remotedebug",
|
|
||||||
title: title,
|
|
||||||
message: msg,
|
|
||||||
buttons: [scan, scanAndRemember, deny],
|
|
||||||
priority: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
prompt.show(data => {
|
|
||||||
let result = data.button;
|
|
||||||
if (result === 0) {
|
|
||||||
resolve(DebuggerServer.AuthenticationResult.ALLOW);
|
|
||||||
} else if (result === 1) {
|
|
||||||
resolve(DebuggerServer.AuthenticationResult.ALLOW_PERSIST);
|
|
||||||
} else {
|
|
||||||
resolve(DebuggerServer.AuthenticationResult.DENY);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* During OOB_CERT authentication, the user must transfer some data through
|
|
||||||
* some out of band mechanism from the client to the server to authenticate
|
|
||||||
* the devices.
|
|
||||||
*
|
|
||||||
* This implementation instructs Fennec to invoke a QR decoder and return the
|
|
||||||
* the data it contains back here.
|
|
||||||
*
|
|
||||||
* @return An object containing:
|
|
||||||
* * sha256: hash(ClientCert)
|
|
||||||
* * k : K(random 128-bit number)
|
|
||||||
* A promise that will be resolved to the above is also allowed.
|
|
||||||
*/
|
|
||||||
receiveOOB() {
|
|
||||||
if (this._receivingOOB) {
|
|
||||||
return this._receivingOOB;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._receivingOOB = WindowEventDispatcher.sendRequestForResult({
|
|
||||||
type: "DevToolsAuth:Scan",
|
|
||||||
}).then(
|
|
||||||
data => {
|
|
||||||
return JSON.parse(data);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
let title = Strings.browser.GetStringFromName(
|
|
||||||
"remoteQRScanFailedPromptTitle"
|
|
||||||
);
|
|
||||||
let msg = Strings.browser.GetStringFromName(
|
|
||||||
"remoteQRScanFailedPromptMessage"
|
|
||||||
);
|
|
||||||
let ok = Strings.browser.GetStringFromName(
|
|
||||||
"remoteQRScanFailedPromptOK"
|
|
||||||
);
|
|
||||||
let prompt = new Prompt({
|
|
||||||
window: null,
|
|
||||||
hint: "remotedebug",
|
|
||||||
title: title,
|
|
||||||
message: msg,
|
|
||||||
buttons: [ok],
|
|
||||||
priority: 1,
|
|
||||||
});
|
|
||||||
prompt.show();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this._receivingOOB.then(() => (this._receivingOOB = null));
|
|
||||||
|
|
||||||
return this._receivingOOB;
|
|
||||||
},
|
|
||||||
|
|
||||||
initServer: function() {
|
|
||||||
DebuggerServer.init();
|
|
||||||
|
|
||||||
// Add browser and Fennec specific actors
|
|
||||||
DebuggerServer.registerAllActors();
|
|
||||||
const {
|
|
||||||
createRootActor,
|
|
||||||
} = require("resource://gre/modules/dbg-browser-actors.js");
|
|
||||||
DebuggerServer.setRootActor(createRootActor);
|
|
||||||
|
|
||||||
// Allow debugging of chrome for any process
|
|
||||||
DebuggerServer.allowChromeProcess = true;
|
|
||||||
DebuggerServer.chromeWindowType = this._windowType;
|
|
||||||
// Force the Server to stay alive even if there are no connections at the moment.
|
|
||||||
DebuggerServer.keepAlive = true;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
RemoteDebugger.allowConnection = RemoteDebugger.allowConnection.bind(
|
|
||||||
RemoteDebugger
|
|
||||||
);
|
|
||||||
RemoteDebugger.receiveOOB = RemoteDebugger.receiveOOB.bind(RemoteDebugger);
|
|
||||||
|
|
||||||
var USBRemoteDebugger = {
|
|
||||||
init() {
|
|
||||||
Services.prefs.addObserver("devtools.", this);
|
|
||||||
|
|
||||||
if (this.isEnabled) {
|
|
||||||
this.start();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
observe(subject, topic, data) {
|
|
||||||
if (topic != "nsPref:changed") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (data) {
|
|
||||||
case "devtools.remote.usb.enabled":
|
|
||||||
Services.prefs.setBoolPref(
|
|
||||||
"devtools.debugger.remote-enabled",
|
|
||||||
RemoteDebugger.isAnyEnabled
|
|
||||||
);
|
|
||||||
if (this.isEnabled) {
|
|
||||||
this.start();
|
|
||||||
} else {
|
|
||||||
this.stop();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "devtools.debugger.remote-port":
|
|
||||||
case "devtools.debugger.unix-domain-socket":
|
|
||||||
if (this.isEnabled) {
|
|
||||||
this.stop();
|
|
||||||
this.start();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
get isEnabled() {
|
|
||||||
return Services.prefs.getBoolPref("devtools.remote.usb.enabled");
|
|
||||||
},
|
|
||||||
|
|
||||||
start: function() {
|
|
||||||
if (this._listener) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoteDebugger.initServer();
|
|
||||||
|
|
||||||
const portOrPath =
|
|
||||||
Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") ||
|
|
||||||
Services.prefs.getIntPref("devtools.debugger.remote-port");
|
|
||||||
|
|
||||||
try {
|
|
||||||
dump("Starting USB debugger on " + portOrPath);
|
|
||||||
const AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT");
|
|
||||||
const authenticator = new AuthenticatorType.Server();
|
|
||||||
authenticator.allowConnection = RemoteDebugger.allowConnection;
|
|
||||||
const socketOptions = { authenticator, portOrPath };
|
|
||||||
this._listener = new SocketListener(DebuggerServer, socketOptions);
|
|
||||||
this._listener.open();
|
|
||||||
} catch (e) {
|
|
||||||
dump("Unable to start USB debugger server: " + e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
stop: function() {
|
|
||||||
if (!this._listener) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this._listener.close();
|
|
||||||
this._listener = null;
|
|
||||||
} catch (e) {
|
|
||||||
dump("Unable to stop USB debugger server: " + e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var WiFiRemoteDebugger = {
|
|
||||||
init() {
|
|
||||||
Services.prefs.addObserver("devtools.", this);
|
|
||||||
|
|
||||||
if (this.isEnabled) {
|
|
||||||
this.start();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
observe(subject, topic, data) {
|
|
||||||
if (topic != "nsPref:changed") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (data) {
|
|
||||||
case "devtools.remote.wifi.enabled":
|
|
||||||
Services.prefs.setBoolPref(
|
|
||||||
"devtools.debugger.remote-enabled",
|
|
||||||
RemoteDebugger.isAnyEnabled
|
|
||||||
);
|
|
||||||
// Allow remote debugging on non-local interfaces when WiFi debug is
|
|
||||||
// enabled
|
|
||||||
// TODO: Bug 1034411: Lock down to WiFi interface only
|
|
||||||
Services.prefs.setBoolPref(
|
|
||||||
"devtools.debugger.force-local",
|
|
||||||
!this.isEnabled
|
|
||||||
);
|
|
||||||
if (this.isEnabled) {
|
|
||||||
this.start();
|
|
||||||
} else {
|
|
||||||
this.stop();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
get isEnabled() {
|
|
||||||
return Services.prefs.getBoolPref("devtools.remote.wifi.enabled");
|
|
||||||
},
|
|
||||||
|
|
||||||
start: function() {
|
|
||||||
if (this._listener) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoteDebugger.initServer();
|
|
||||||
|
|
||||||
try {
|
|
||||||
dump("Starting WiFi debugger");
|
|
||||||
const AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT");
|
|
||||||
const authenticator = new AuthenticatorType.Server();
|
|
||||||
authenticator.allowConnection = RemoteDebugger.allowConnection;
|
|
||||||
authenticator.receiveOOB = RemoteDebugger.receiveOOB;
|
|
||||||
const socketOptions = {
|
|
||||||
authenticator,
|
|
||||||
discoverable: true,
|
|
||||||
encryption: true,
|
|
||||||
portOrPath: -1,
|
|
||||||
};
|
|
||||||
this._listener = new SocketListener(DebuggerServer, socketOptions);
|
|
||||||
this._listener.open();
|
|
||||||
let port = this._listener.port;
|
|
||||||
dump("Started WiFi debugger on " + port);
|
|
||||||
} catch (e) {
|
|
||||||
dump("Unable to start WiFi debugger server: " + e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
stop: function() {
|
|
||||||
if (!this._listener) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this._listener.close();
|
|
||||||
this._listener = null;
|
|
||||||
} catch (e) {
|
|
||||||
dump("Unable to stop WiFi debugger server: " + e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
const {EventDispatcher} = ChromeUtils.import("resource://gre/modules/Messaging.jsm");
|
|
||||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
// Include the build date and a warning about Telemetry
|
|
||||||
// if this is an "a#" (nightly or aurora) build
|
|
||||||
#expand const version = "__MOZ_APP_VERSION_DISPLAY__";
|
|
||||||
if (/a\d+$/.test(version)) {
|
|
||||||
let buildID = Services.appinfo.appBuildID;
|
|
||||||
let buildDate = buildID.slice(0, 4) + "-" + buildID.slice(4, 6) + "-" + buildID.slice(6, 8);
|
|
||||||
let br = document.createElement("br");
|
|
||||||
let versionPara = document.getElementById("version");
|
|
||||||
versionPara.appendChild(br);
|
|
||||||
let date = document.createTextNode("(" + buildDate + ")");
|
|
||||||
versionPara.appendChild(date);
|
|
||||||
document.getElementById("telemetry").hidden = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Include the Distribution information if available
|
|
||||||
try {
|
|
||||||
let distroId = Services.prefs.getCharPref("distribution.id");
|
|
||||||
if (distroId) {
|
|
||||||
let distroVersion = Services.prefs.getCharPref("distribution.version");
|
|
||||||
let distroIdField = document.getElementById("distributionID");
|
|
||||||
distroIdField.textContent = distroId + " - " + distroVersion;
|
|
||||||
distroIdField.hidden = false;
|
|
||||||
|
|
||||||
let distroAbout = Services.prefs.getStringPref("distribution.about");
|
|
||||||
let distroField = document.getElementById("distributionAbout");
|
|
||||||
distroField.textContent = distroAbout;
|
|
||||||
distroField.hidden = false;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Pref is unset
|
|
||||||
}
|
|
||||||
|
|
||||||
// get URLs from prefs
|
|
||||||
try {
|
|
||||||
let formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
|
|
||||||
|
|
||||||
let links = [
|
|
||||||
{id: "releaseNotesURL", pref: "app.releaseNotesURL"},
|
|
||||||
{id: "supportURL", pref: "app.supportURL"},
|
|
||||||
{id: "faqURL", pref: "app.faqURL"},
|
|
||||||
{id: "privacyURL", pref: "app.privacyURL"},
|
|
||||||
{id: "creditsURL", pref: "app.creditsURL"},
|
|
||||||
];
|
|
||||||
|
|
||||||
links.forEach(function(link) {
|
|
||||||
let url = formatter.formatURLPref(link.pref);
|
|
||||||
let element = document.getElementById(link.id);
|
|
||||||
if (element) {
|
|
||||||
element.setAttribute("href", url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (ex) {}
|
|
||||||
|
|
||||||
#ifdef MOZ_UPDATER
|
|
||||||
function expectUpdateResult() {
|
|
||||||
EventDispatcher.instance.registerListener(function listener(event, data, callback) {
|
|
||||||
EventDispatcher.instance.unregisterListener(listener, event);
|
|
||||||
showUpdateMessage(data.result);
|
|
||||||
}, "Update:CheckResult");
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkForUpdates() {
|
|
||||||
showCheckingMessage();
|
|
||||||
expectUpdateResult();
|
|
||||||
|
|
||||||
EventDispatcher.instance.sendRequest({ type: "Update:Check" });
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadUpdate() {
|
|
||||||
expectUpdateResult();
|
|
||||||
|
|
||||||
EventDispatcher.instance.sendRequest({ type: "Update:Download" });
|
|
||||||
}
|
|
||||||
|
|
||||||
function installUpdate() {
|
|
||||||
showCheckAction();
|
|
||||||
|
|
||||||
EventDispatcher.instance.sendRequest({ type: "Update:Install" });
|
|
||||||
}
|
|
||||||
|
|
||||||
let updateLink = document.getElementById("updateLink");
|
|
||||||
let checkingSpan = document.getElementById("update-message-checking");
|
|
||||||
let noneSpan = document.getElementById("update-message-none");
|
|
||||||
let foundSpan = document.getElementById("update-message-found");
|
|
||||||
let downloadingSpan = document.getElementById("update-message-downloading");
|
|
||||||
let downloadedSpan = document.getElementById("update-message-downloaded");
|
|
||||||
|
|
||||||
updateLink.onclick = checkForUpdates;
|
|
||||||
foundSpan.onclick = downloadUpdate;
|
|
||||||
downloadedSpan.onclick = installUpdate;
|
|
||||||
|
|
||||||
function showCheckAction() {
|
|
||||||
checkingSpan.style.display = "none";
|
|
||||||
noneSpan.style.display = "none";
|
|
||||||
foundSpan.style.display = "none";
|
|
||||||
downloadingSpan.style.display = "none";
|
|
||||||
downloadedSpan.style.display = "none";
|
|
||||||
updateLink.style.display = "block";
|
|
||||||
}
|
|
||||||
|
|
||||||
function showCheckingMessage() {
|
|
||||||
updateLink.style.display = "none";
|
|
||||||
noneSpan.style.display = "none";
|
|
||||||
foundSpan.style.display = "none";
|
|
||||||
downloadingSpan.style.display = "none";
|
|
||||||
downloadedSpan.style.display = "none";
|
|
||||||
checkingSpan.style.display = "block";
|
|
||||||
}
|
|
||||||
|
|
||||||
function showUpdateMessage(aResult) {
|
|
||||||
updateLink.style.display = "none";
|
|
||||||
checkingSpan.style.display = "none";
|
|
||||||
noneSpan.style.display = "none";
|
|
||||||
foundSpan.style.display = "none";
|
|
||||||
downloadingSpan.style.display = "none";
|
|
||||||
downloadedSpan.style.display = "none";
|
|
||||||
|
|
||||||
// the aResult values come from mobile/android/base/UpdateServiceHelper.java
|
|
||||||
switch (aResult) {
|
|
||||||
case "NOT_AVAILABLE":
|
|
||||||
noneSpan.style.display = "block";
|
|
||||||
setTimeout(showCheckAction, 2000);
|
|
||||||
break;
|
|
||||||
case "AVAILABLE":
|
|
||||||
foundSpan.style.display = "block";
|
|
||||||
break;
|
|
||||||
case "DOWNLOADING":
|
|
||||||
downloadingSpan.style.display = "block";
|
|
||||||
expectUpdateResult();
|
|
||||||
break;
|
|
||||||
case "DOWNLOADED":
|
|
||||||
downloadedSpan.style.display = "block";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", init);
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<!DOCTYPE html [
|
|
||||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
|
||||||
%brandDTD;
|
|
||||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
|
||||||
%globalDTD;
|
|
||||||
<!ENTITY % fennecDTD SYSTEM "chrome://browser/locale/about.dtd">
|
|
||||||
%fennecDTD;
|
|
||||||
]>
|
|
||||||
|
|
||||||
<!-- 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/. -->
|
|
||||||
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=480; initial-scale=.6667; user-scalable=no"/>
|
|
||||||
<title>&aboutPage.title;</title>
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/aboutPage.css" type="text/css"/>
|
|
||||||
<link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body dir="&locale.dir;">
|
|
||||||
<div id="header" dir="ltr">
|
|
||||||
<div id="wordmark"></div>
|
|
||||||
#expand <p id="version">__MOZ_APP_VERSION_DISPLAY__</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="banner">
|
|
||||||
<div id="logo"/>
|
|
||||||
#ifdef MOZ_UPDATER
|
|
||||||
<div id="updateBox">
|
|
||||||
<a id="updateLink" href="">&aboutPage.checkForUpdates.link;</a>
|
|
||||||
<span id="update-message-checking">&aboutPage.checkForUpdates.checking;</span>
|
|
||||||
<span id="update-message-none">&aboutPage.checkForUpdates.none;</span>
|
|
||||||
<span id="update-message-found">&aboutPage.checkForUpdates.available2;</span>
|
|
||||||
<span id="update-message-downloading">&aboutPage.checkForUpdates.downloading;</span>
|
|
||||||
<span id="update-message-downloaded">&aboutPage.checkForUpdates.downloaded2;</span>
|
|
||||||
</div>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
<div id="messages">
|
|
||||||
<p id="distributionAbout" hidden="true"/>
|
|
||||||
<p id="distributionID" hidden="true"/>
|
|
||||||
<p id="telemetry" hidden="true">
|
|
||||||
&aboutPage.warningVersion;
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul id="aboutLinks">
|
|
||||||
<div class="top-border"></div>
|
|
||||||
<li><a id="faqURL">&aboutPage.faq.label;</a></li>
|
|
||||||
<li><a id="supportURL">&aboutPage.support.label;</a></li>
|
|
||||||
<li><a id="privacyURL">&aboutPage.privacyPolicy.label;</a></li>
|
|
||||||
<li><a href="about:rights">&aboutPage.rights.label;</a></li>
|
|
||||||
#ifndef NIGHTLY_BUILD
|
|
||||||
#ifndef FENNEC_NIGHTLY
|
|
||||||
<li><a id="releaseNotesURL">&aboutPage.relNotes.label;</a></li>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
<li><a id="creditsURL">&aboutPage.credits.label;</a></li>
|
|
||||||
<li><a href="about:license">&aboutPage.license.label;</a></li>
|
|
||||||
<div class="bottom-border"></div>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
#ifdef RELEASE_OR_BETA
|
|
||||||
#ifndef FENNEC_NIGHTLY
|
|
||||||
<div id="aboutDetails">
|
|
||||||
<p>&aboutPage.logoTrademark;</p>
|
|
||||||
</div>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
<script type="application/javascript" src="chrome://browser/content/about.js" />
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,377 +0,0 @@
|
||||||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrap a remote fxa-content-server.
|
|
||||||
*
|
|
||||||
* An about:accounts tab loads and displays an fxa-content-server page,
|
|
||||||
* depending on the current Android Account status and an optional 'action'
|
|
||||||
* parameter.
|
|
||||||
*
|
|
||||||
* We show a spinner while the remote iframe is loading. We expect the
|
|
||||||
* WebChannel message listening to the fxa-content-server to send this tab's
|
|
||||||
* <browser>'s messageManager a LOADED message when the remote iframe provides
|
|
||||||
* the WebChannel LOADED message. See the messageManager registration and the
|
|
||||||
* |loadedDeferred| promise. This loosely couples the WebChannel implementation
|
|
||||||
* and about:accounts! (We need this coupling in order to distinguish
|
|
||||||
* WebChannel LOADED messages produced by multiple about:accounts tabs.)
|
|
||||||
*
|
|
||||||
* We capture error conditions by accessing the inner nsIWebNavigation of the
|
|
||||||
* iframe directly.
|
|
||||||
*/
|
|
||||||
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const { Accounts } = ChromeUtils.import("resource://gre/modules/Accounts.jsm");
|
|
||||||
const { PromiseUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/PromiseUtils.jsm"
|
|
||||||
);
|
|
||||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
const { XPCOMUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/XPCOMUtils.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
const ACTION_URL_PARAM = "action";
|
|
||||||
|
|
||||||
const COMMAND_LOADED = "fxaccounts:loaded";
|
|
||||||
|
|
||||||
const log = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/AndroidLog.jsm",
|
|
||||||
{}
|
|
||||||
).AndroidLog.bind("FxAccounts");
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyServiceGetter(
|
|
||||||
this,
|
|
||||||
"ParentalControls",
|
|
||||||
"@mozilla.org/parental-controls-service;1",
|
|
||||||
"nsIParentalControlsService"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Shows the toplevel element with |id| to be shown - all other top-level
|
|
||||||
// elements are hidden.
|
|
||||||
// If |id| is 'spinner', then 'remote' is also shown, with opacity 0.
|
|
||||||
function show(id) {
|
|
||||||
let allTop = document.querySelectorAll(".toplevel");
|
|
||||||
for (let elt of allTop) {
|
|
||||||
if (elt.getAttribute("id") == id) {
|
|
||||||
elt.style.display = "block";
|
|
||||||
} else {
|
|
||||||
elt.style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (id == "spinner") {
|
|
||||||
document.getElementById("remote").style.display = "block";
|
|
||||||
document.getElementById("remote").style.opacity = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Each time we try to load the remote <iframe>, loadedDeferred is replaced. It
|
|
||||||
// is resolved by a LOADED message, and rejected by a failure to load.
|
|
||||||
var loadedDeferred = null;
|
|
||||||
|
|
||||||
// We have a new load starting. Replace the existing promise with a new one,
|
|
||||||
// and queue up the transition to remote content.
|
|
||||||
function deferTransitionToRemoteAfterLoaded() {
|
|
||||||
log.d("Waiting for LOADED message.");
|
|
||||||
|
|
||||||
loadedDeferred = PromiseUtils.defer();
|
|
||||||
loadedDeferred.promise
|
|
||||||
.then(() => {
|
|
||||||
log.d("Got LOADED message!");
|
|
||||||
document.getElementById("remote").style.opacity = 0;
|
|
||||||
show("remote");
|
|
||||||
document.getElementById("remote").style.opacity = 1;
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
log.w("Did not get LOADED message: " + e.toString());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLoadedMessage(message) {
|
|
||||||
loadedDeferred.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
var wrapper = {
|
|
||||||
iframe: null,
|
|
||||||
|
|
||||||
url: null,
|
|
||||||
|
|
||||||
init: function(url) {
|
|
||||||
this.url = url;
|
|
||||||
deferTransitionToRemoteAfterLoaded();
|
|
||||||
|
|
||||||
let iframe = document.getElementById("remote");
|
|
||||||
this.iframe = iframe;
|
|
||||||
let docShell = this.iframe.frameLoader.docShell;
|
|
||||||
docShell.QueryInterface(Ci.nsIWebProgress);
|
|
||||||
docShell.addProgressListener(
|
|
||||||
this.iframeListener,
|
|
||||||
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT |
|
|
||||||
Ci.nsIWebProgress.NOTIFY_LOCATION
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set the iframe's location with loadURI/LOAD_FLAGS_BYPASS_HISTORY to
|
|
||||||
// avoid having a new history entry being added.
|
|
||||||
let webNav = iframe.frameLoader.docShell.QueryInterface(
|
|
||||||
Ci.nsIWebNavigation
|
|
||||||
);
|
|
||||||
let loadURIOptions = {
|
|
||||||
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
|
||||||
loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY,
|
|
||||||
};
|
|
||||||
webNav.loadURI(url, loadURIOptions);
|
|
||||||
},
|
|
||||||
|
|
||||||
retry: function() {
|
|
||||||
deferTransitionToRemoteAfterLoaded();
|
|
||||||
|
|
||||||
let webNav = this.iframe.frameLoader.docShell.QueryInterface(
|
|
||||||
Ci.nsIWebNavigation
|
|
||||||
);
|
|
||||||
let loadURIOptions = {
|
|
||||||
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
|
||||||
loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY,
|
|
||||||
};
|
|
||||||
webNav.loadURI(this.url, loadURIOptions);
|
|
||||||
},
|
|
||||||
|
|
||||||
iframeListener: {
|
|
||||||
QueryInterface: ChromeUtils.generateQI([
|
|
||||||
Ci.nsIWebProgressListener,
|
|
||||||
Ci.nsISupportsWeakReference,
|
|
||||||
]),
|
|
||||||
|
|
||||||
onStateChange: function(aWebProgress, aRequest, aState, aStatus) {
|
|
||||||
let failure = false;
|
|
||||||
|
|
||||||
// Captive portals sometimes redirect users
|
|
||||||
if (aState & Ci.nsIWebProgressListener.STATE_REDIRECTING) {
|
|
||||||
failure = true;
|
|
||||||
} else if (aState & Ci.nsIWebProgressListener.STATE_STOP) {
|
|
||||||
if (aRequest instanceof Ci.nsIHttpChannel) {
|
|
||||||
try {
|
|
||||||
failure = aRequest.responseStatus != 200;
|
|
||||||
} catch (e) {
|
|
||||||
failure = aStatus != Cr.NS_OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calling cancel() will raise some OnStateChange notifications by itself,
|
|
||||||
// so avoid doing that more than once
|
|
||||||
if (failure && aStatus != Cr.NS_BINDING_ABORTED) {
|
|
||||||
aRequest.cancel(Cr.NS_BINDING_ABORTED);
|
|
||||||
// Since after a promise is fulfilled, subsequent fulfillments are
|
|
||||||
// treated as no-ops, we don't care that we might see multiple failures
|
|
||||||
// due to multiple listener callbacks. (It's not easy to extract this
|
|
||||||
// from the Promises spec, but it is widely quoted. Start with
|
|
||||||
// http://stackoverflow.com/a/18218542.)
|
|
||||||
loadedDeferred.reject(new Error("Failed in onStateChange!"));
|
|
||||||
show("networkError");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
|
|
||||||
if (
|
|
||||||
aRequest &&
|
|
||||||
aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE
|
|
||||||
) {
|
|
||||||
aRequest.cancel(Cr.NS_BINDING_ABORTED);
|
|
||||||
// As above, we're not concerned by multiple listener callbacks.
|
|
||||||
loadedDeferred.reject(new Error("Failed in onLocationChange!"));
|
|
||||||
show("networkError");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function retry() {
|
|
||||||
log.i("Retrying.");
|
|
||||||
show("spinner");
|
|
||||||
wrapper.retry();
|
|
||||||
}
|
|
||||||
|
|
||||||
function openPrefs() {
|
|
||||||
log.i("Opening Sync preferences.");
|
|
||||||
// If an Android Account exists, this will open the Status Activity.
|
|
||||||
// Otherwise, it will begin the Get Started flow. This should only be shown
|
|
||||||
// when an Account actually exists.
|
|
||||||
Accounts.launchSetup();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getURLForAction(action, urlParams) {
|
|
||||||
let url = Services.urlFormatter.formatURLPref(
|
|
||||||
"identity.fxaccounts.remote.webchannel.uri"
|
|
||||||
);
|
|
||||||
url = url + (url.endsWith("/") ? "" : "/") + action;
|
|
||||||
const CONTEXT = "fx_fennec_v1";
|
|
||||||
// The only service managed by Fennec, to date, is Firefox Sync.
|
|
||||||
const SERVICE = "sync";
|
|
||||||
urlParams = urlParams || new URLSearchParams("");
|
|
||||||
urlParams.set("service", SERVICE);
|
|
||||||
urlParams.set("context", CONTEXT);
|
|
||||||
// Ideally we'd just merge urlParams with new URL(url).searchParams, but our
|
|
||||||
// URLSearchParams implementation doesn't support iteration (bug 1085284).
|
|
||||||
let urlParamStr = urlParams.toString();
|
|
||||||
if (urlParamStr) {
|
|
||||||
url += (url.includes("?") ? "&" : "?") + urlParamStr;
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDisplayedEmail(user) {
|
|
||||||
let emailDiv = document.getElementById("email");
|
|
||||||
if (emailDiv && user) {
|
|
||||||
emailDiv.textContent = user.email;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
// Test for restrictions before getFirefoxAccount(), since that will fail if
|
|
||||||
// we are restricted.
|
|
||||||
if (!ParentalControls.isAllowed(ParentalControls.MODIFY_ACCOUNTS)) {
|
|
||||||
// It's better to log and show an error message than to invite user
|
|
||||||
// confusion by removing about:accounts entirely. That is, if the user is
|
|
||||||
// restricted, this way they'll discover as much and may be able to get
|
|
||||||
// out of their restricted profile. If we remove about:accounts entirely,
|
|
||||||
// it will look like Fennec is buggy, and the user will be very confused.
|
|
||||||
log.e(
|
|
||||||
"This profile cannot connect to Firefox Accounts: showing restricted error."
|
|
||||||
);
|
|
||||||
show("restrictedError");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Accounts.getFirefoxAccount()
|
|
||||||
.then(user => {
|
|
||||||
// It's possible for the window to start closing before getting the user
|
|
||||||
// completes. Tests in particular can cause this.
|
|
||||||
if (window.closed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDisplayedEmail(user);
|
|
||||||
|
|
||||||
// Ideally we'd use new URL(document.URL).searchParams, but for about: URIs,
|
|
||||||
// searchParams is empty.
|
|
||||||
let urlParams = new URLSearchParams(document.URL.split("?")[1] || "");
|
|
||||||
let action = urlParams.get(ACTION_URL_PARAM);
|
|
||||||
urlParams.delete(ACTION_URL_PARAM);
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case "signup":
|
|
||||||
if (user) {
|
|
||||||
// Asking to sign-up when already signed in just shows prefs.
|
|
||||||
show("prefs");
|
|
||||||
} else {
|
|
||||||
show("spinner");
|
|
||||||
wrapper.init(getURLForAction("signup", urlParams));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "signin":
|
|
||||||
if (user) {
|
|
||||||
// Asking to sign-in when already signed in just shows prefs.
|
|
||||||
show("prefs");
|
|
||||||
} else {
|
|
||||||
show("spinner");
|
|
||||||
wrapper.init(getURLForAction("signin", urlParams));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "force_auth":
|
|
||||||
if (user) {
|
|
||||||
show("spinner");
|
|
||||||
urlParams.set("email", user.email); // In future, pin using the UID.
|
|
||||||
wrapper.init(getURLForAction("force_auth", urlParams));
|
|
||||||
} else {
|
|
||||||
show("spinner");
|
|
||||||
wrapper.init(getURLForAction("signup", urlParams));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "manage":
|
|
||||||
if (user) {
|
|
||||||
show("spinner");
|
|
||||||
urlParams.set("email", user.email); // In future, pin using the UID.
|
|
||||||
wrapper.init(getURLForAction("settings", urlParams));
|
|
||||||
} else {
|
|
||||||
show("spinner");
|
|
||||||
wrapper.init(getURLForAction("signup", urlParams));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "avatar":
|
|
||||||
if (user) {
|
|
||||||
show("spinner");
|
|
||||||
urlParams.set("email", user.email); // In future, pin using the UID.
|
|
||||||
wrapper.init(getURLForAction("settings/avatar/change", urlParams));
|
|
||||||
} else {
|
|
||||||
show("spinner");
|
|
||||||
wrapper.init(getURLForAction("signup", urlParams));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Unrecognized or no action specified.
|
|
||||||
if (action) {
|
|
||||||
log.w("Ignoring unrecognized action: " + action);
|
|
||||||
}
|
|
||||||
if (user) {
|
|
||||||
show("prefs");
|
|
||||||
} else {
|
|
||||||
show("spinner");
|
|
||||||
wrapper.init(getURLForAction("signup", urlParams));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
log.e("Failed to get the signed in user: " + e.toString());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener(
|
|
||||||
"DOMContentLoaded",
|
|
||||||
function() {
|
|
||||||
init();
|
|
||||||
var buttonRetry = document.getElementById("buttonRetry");
|
|
||||||
buttonRetry.addEventListener("click", retry);
|
|
||||||
|
|
||||||
var buttonOpenPrefs = document.getElementById("buttonOpenPrefs");
|
|
||||||
buttonOpenPrefs.addEventListener("click", openPrefs);
|
|
||||||
},
|
|
||||||
{ capture: true, once: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// This window is contained in a XUL <browser> element. Return the
|
|
||||||
// messageManager of that <browser> element, or null.
|
|
||||||
function getBrowserMessageManager() {
|
|
||||||
let browser = window.docShell.rootTreeItem.domWindow.BrowserApp.getBrowserForDocument(
|
|
||||||
document
|
|
||||||
);
|
|
||||||
if (browser) {
|
|
||||||
return browser.messageManager;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a single listener for 'loaded' messages from the iframe in this
|
|
||||||
// <browser>. These 'loaded' messages are ferried from the WebChannel to just
|
|
||||||
// this <browser>.
|
|
||||||
var mm = getBrowserMessageManager();
|
|
||||||
if (mm) {
|
|
||||||
mm.addMessageListener(COMMAND_LOADED, handleLoadedMessage);
|
|
||||||
} else {
|
|
||||||
log.e("No messageManager, not listening for LOADED message!");
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("unload", function(event) {
|
|
||||||
try {
|
|
||||||
let mm = getBrowserMessageManager();
|
|
||||||
if (mm) {
|
|
||||||
mm.removeMessageListener(COMMAND_LOADED, handleLoadedMessage);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// This could fail if the page is being torn down, the tab is being
|
|
||||||
// destroyed, etc.
|
|
||||||
log.w("Not removing listener for LOADED message: " + e.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- 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/. -->
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
|
||||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
|
|
||||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
|
||||||
%brandDTD;
|
|
||||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
|
|
||||||
%globalDTD;
|
|
||||||
<!ENTITY % aboutDTD SYSTEM "chrome://browser/locale/aboutAccounts.dtd">
|
|
||||||
%aboutDTD;
|
|
||||||
]>
|
|
||||||
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" dir="&locale.dir;">
|
|
||||||
<head>
|
|
||||||
<title>Firefox Sync</title>
|
|
||||||
<meta name="viewport" content="width=device-width; user-scalable=0" />
|
|
||||||
<link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/spinner.css" type="text/css"/>
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/>
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/aboutAccounts.css" type="text/css"/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="spinner" class="toplevel">
|
|
||||||
<div class="container flex-column">
|
|
||||||
<!-- Empty text-container for spacing. -->
|
|
||||||
<div class="text-container flex-column" />
|
|
||||||
|
|
||||||
<div class="mui-refresh-main">
|
|
||||||
<div class="mui-refresh-wrapper">
|
|
||||||
<div class="mui-spinner-wrapper">
|
|
||||||
<div class="mui-spinner-main">
|
|
||||||
<div class="mui-spinner-left">
|
|
||||||
<div class="mui-half-circle-left" />
|
|
||||||
</div>
|
|
||||||
<div class="mui-spinner-right">
|
|
||||||
<div class="mui-half-circle-right" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<iframe mozframetype="content" id="remote" class="toplevel" />
|
|
||||||
|
|
||||||
<div id="prefs" class="toplevel">
|
|
||||||
<div class="container flex-column">
|
|
||||||
<div class="text-container flex-column">
|
|
||||||
<div class="text">&aboutAccounts.connected.title;</div>
|
|
||||||
<div class="hint">&aboutAccounts.connected.description;</div>
|
|
||||||
<div id="email" class="hint"></div>
|
|
||||||
</div>
|
|
||||||
<a id="buttonOpenPrefs" tabindex="0" href="#">&aboutAccounts.syncPreferences.label;</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="networkError" class="toplevel">
|
|
||||||
<div class="container flex-column">
|
|
||||||
<div class="text-container flex-column">
|
|
||||||
<div class="text">&aboutAccounts.noConnection.title;</div>
|
|
||||||
</div>
|
|
||||||
<div class="button-row">
|
|
||||||
<button id="buttonRetry" class="button" tabindex="1">&aboutAccounts.retry.label;</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="restrictedError" class="toplevel">
|
|
||||||
<div class="container flex-column">
|
|
||||||
<div class="text-container flex-column">
|
|
||||||
<div class="text">&aboutAccounts.restrictedError.title;</div>
|
|
||||||
<div class="hint">&aboutAccounts.restrictedError.description;</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="application/javascript" src="chrome://browser/content/aboutAccounts.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,897 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* globals gChromeWin */
|
|
||||||
|
|
||||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
const { AddonManager } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/AddonManager.jsm"
|
|
||||||
);
|
|
||||||
const { XPCOMUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/XPCOMUtils.jsm"
|
|
||||||
);
|
|
||||||
const { EventDispatcher } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/Messaging.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
const AMO_ICON = "chrome://browser/skin/images/amo-logo.png";
|
|
||||||
const UPDATE_INDICATOR = "chrome://browser/skin/images/extension-update.svg";
|
|
||||||
|
|
||||||
var gStringBundle = Services.strings.createBundle(
|
|
||||||
"chrome://browser/locale/aboutAddons.properties"
|
|
||||||
);
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(window, "gChromeWin", function() {
|
|
||||||
return window.docShell.rootTreeItem.domWindow;
|
|
||||||
});
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
window,
|
|
||||||
"Preferences",
|
|
||||||
"resource://gre/modules/Preferences.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
var ContextMenus = {
|
|
||||||
target: null,
|
|
||||||
|
|
||||||
init: function() {
|
|
||||||
document.addEventListener("contextmenu", this);
|
|
||||||
|
|
||||||
document
|
|
||||||
.getElementById("contextmenu-enable")
|
|
||||||
.addEventListener("click", ContextMenus.enable.bind(this));
|
|
||||||
document
|
|
||||||
.getElementById("contextmenu-disable")
|
|
||||||
.addEventListener("click", ContextMenus.disable.bind(this));
|
|
||||||
document
|
|
||||||
.getElementById("contextmenu-uninstall")
|
|
||||||
.addEventListener("click", ContextMenus.uninstall.bind(this));
|
|
||||||
|
|
||||||
// XXX - Hack to fix bug 985867 for now
|
|
||||||
document.addEventListener("touchstart", function() {});
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEvent: function(event) {
|
|
||||||
// store the target of context menu events so that we know which app to act on
|
|
||||||
this.target = event.target;
|
|
||||||
while (!this.target.hasAttribute("contextmenu")) {
|
|
||||||
this.target = this.target.parentNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.target) {
|
|
||||||
document
|
|
||||||
.getElementById("contextmenu-enable")
|
|
||||||
.setAttribute("hidden", "true");
|
|
||||||
document
|
|
||||||
.getElementById("contextmenu-disable")
|
|
||||||
.setAttribute("hidden", "true");
|
|
||||||
document
|
|
||||||
.getElementById("contextmenu-uninstall")
|
|
||||||
.setAttribute("hidden", "true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let addon = this.target.addon;
|
|
||||||
if (addon.scope == AddonManager.SCOPE_APPLICATION) {
|
|
||||||
document
|
|
||||||
.getElementById("contextmenu-uninstall")
|
|
||||||
.setAttribute("hidden", "true");
|
|
||||||
} else {
|
|
||||||
document
|
|
||||||
.getElementById("contextmenu-uninstall")
|
|
||||||
.removeAttribute("hidden");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide the enable/disable context menu items if the add-on was disabled by
|
|
||||||
// Firefox (e.g. unsigned or blocklisted add-on).
|
|
||||||
if (addon.appDisabled) {
|
|
||||||
document
|
|
||||||
.getElementById("contextmenu-enable")
|
|
||||||
.setAttribute("hidden", "true");
|
|
||||||
document
|
|
||||||
.getElementById("contextmenu-disable")
|
|
||||||
.setAttribute("hidden", "true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let enabled = this.target.getAttribute("isDisabled") != "true";
|
|
||||||
if (enabled) {
|
|
||||||
document
|
|
||||||
.getElementById("contextmenu-enable")
|
|
||||||
.setAttribute("hidden", "true");
|
|
||||||
document.getElementById("contextmenu-disable").removeAttribute("hidden");
|
|
||||||
} else {
|
|
||||||
document.getElementById("contextmenu-enable").removeAttribute("hidden");
|
|
||||||
document
|
|
||||||
.getElementById("contextmenu-disable")
|
|
||||||
.setAttribute("hidden", "true");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
enable: function(event) {
|
|
||||||
Addons.setEnabled(true, this.target.addon);
|
|
||||||
this.target = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
disable: function(event) {
|
|
||||||
Addons.setEnabled(false, this.target.addon);
|
|
||||||
this.target = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
uninstall: function(event) {
|
|
||||||
Addons.uninstall(this.target.addon);
|
|
||||||
this.target = null;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function sendEMPong() {
|
|
||||||
Services.obs.notifyObservers(window, "EM-pong");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
window.addEventListener("popstate", onPopState);
|
|
||||||
|
|
||||||
AddonManager.addInstallListener(Addons);
|
|
||||||
AddonManager.addAddonListener(Addons);
|
|
||||||
|
|
||||||
await Addons.init();
|
|
||||||
showAddons();
|
|
||||||
ContextMenus.init();
|
|
||||||
|
|
||||||
Services.obs.addObserver(sendEMPong, "EM-ping");
|
|
||||||
|
|
||||||
// The addons list has been loaded and rendered, send a notification
|
|
||||||
// if the openOptionsPage is waiting to be able to select an addon details page.
|
|
||||||
Services.obs.notifyObservers(window, "EM-loaded");
|
|
||||||
}
|
|
||||||
|
|
||||||
function uninit() {
|
|
||||||
AddonManager.removeInstallListener(Addons);
|
|
||||||
AddonManager.removeAddonListener(Addons);
|
|
||||||
|
|
||||||
Services.obs.removeObserver(sendEMPong, "EM-ping");
|
|
||||||
}
|
|
||||||
|
|
||||||
function openLink(url) {
|
|
||||||
let BrowserApp = gChromeWin.BrowserApp;
|
|
||||||
BrowserApp.addTab(url, {
|
|
||||||
selected: true,
|
|
||||||
parentId: BrowserApp.selectedTab.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function openOptionsInTab(url) {
|
|
||||||
let BrowserApp = gChromeWin.BrowserApp;
|
|
||||||
BrowserApp.selectOrAddTab(url, {
|
|
||||||
startsWith: true,
|
|
||||||
selected: true,
|
|
||||||
parentId: BrowserApp.selectedTab.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPopState(aEvent) {
|
|
||||||
// Called when back/forward is used to change the state of the page
|
|
||||||
if (aEvent.state) {
|
|
||||||
// Show the detail page for an addon
|
|
||||||
const listItem = Addons._getElementForAddon(aEvent.state.id);
|
|
||||||
if (listItem) {
|
|
||||||
Addons.showDetails(listItem);
|
|
||||||
} else {
|
|
||||||
// If the addon doesn't exist anymore, go back in the history.
|
|
||||||
history.back();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Clear any previous detail addon
|
|
||||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
|
||||||
detailItem.addon = null;
|
|
||||||
|
|
||||||
showAddons();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showAddonDetails(addonId) {
|
|
||||||
const listItem = Addons._getElementForAddon(addonId);
|
|
||||||
if (listItem) {
|
|
||||||
Addons.showDetails(listItem);
|
|
||||||
history.pushState({ id: addonId }, document.title);
|
|
||||||
} else {
|
|
||||||
throw new Error(`Addon not found: ${addonId}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showAddons() {
|
|
||||||
// Hide the addon options and show the addons list
|
|
||||||
let details = document.querySelector("#addons-details");
|
|
||||||
details.classList.add("hidden");
|
|
||||||
let list = document.querySelector("#addons-list");
|
|
||||||
list.classList.remove("hidden");
|
|
||||||
document.documentElement.removeAttribute("details");
|
|
||||||
|
|
||||||
// Clean the optionsBox content when switching to the add-ons list view.
|
|
||||||
let optionsBox = document.querySelector(
|
|
||||||
"#addons-details > .addon-item .options-box"
|
|
||||||
);
|
|
||||||
optionsBox.innerHTML = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function showAddonOptions() {
|
|
||||||
// Hide the addon list and show the addon options
|
|
||||||
let list = document.querySelector("#addons-list");
|
|
||||||
list.classList.add("hidden");
|
|
||||||
let details = document.querySelector("#addons-details");
|
|
||||||
details.classList.remove("hidden");
|
|
||||||
document.documentElement.setAttribute("details", "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
var Addons = {
|
|
||||||
_restartCount: 0,
|
|
||||||
|
|
||||||
_createItem: function _createItem(aAddon) {
|
|
||||||
let outer = document.createElement("div");
|
|
||||||
outer.setAttribute("addonID", aAddon.id);
|
|
||||||
outer.className = "addon-item list-item";
|
|
||||||
outer.setAttribute("role", "button");
|
|
||||||
outer.setAttribute("contextmenu", "addonmenu");
|
|
||||||
outer.addEventListener(
|
|
||||||
"click",
|
|
||||||
() => {
|
|
||||||
this.showDetails(outer);
|
|
||||||
history.pushState({ id: aAddon.id }, document.title);
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
let img = document.createElement("img");
|
|
||||||
img.className = "icon";
|
|
||||||
img.setAttribute("src", aAddon.iconURL || AMO_ICON);
|
|
||||||
outer.appendChild(img);
|
|
||||||
|
|
||||||
let inner = document.createElement("div");
|
|
||||||
inner.className = "inner";
|
|
||||||
|
|
||||||
let details = document.createElement("div");
|
|
||||||
details.className = "details";
|
|
||||||
inner.appendChild(details);
|
|
||||||
|
|
||||||
let titlePart = document.createElement("div");
|
|
||||||
titlePart.textContent = aAddon.name;
|
|
||||||
titlePart.className = "title";
|
|
||||||
details.appendChild(titlePart);
|
|
||||||
|
|
||||||
let versionPart = document.createElement("div");
|
|
||||||
versionPart.textContent = aAddon.version;
|
|
||||||
versionPart.className = "version";
|
|
||||||
details.appendChild(versionPart);
|
|
||||||
|
|
||||||
if ("description" in aAddon) {
|
|
||||||
let descPart = document.createElement("div");
|
|
||||||
descPart.textContent = aAddon.description;
|
|
||||||
descPart.className = "description";
|
|
||||||
inner.appendChild(descPart);
|
|
||||||
}
|
|
||||||
|
|
||||||
outer.appendChild(inner);
|
|
||||||
|
|
||||||
let update = document.createElement("img");
|
|
||||||
update.className = "update-indicator";
|
|
||||||
update.setAttribute("src", UPDATE_INDICATOR);
|
|
||||||
outer.appendChild(update);
|
|
||||||
|
|
||||||
return outer;
|
|
||||||
},
|
|
||||||
|
|
||||||
_createBrowseItem: function _createBrowseItem() {
|
|
||||||
let outer = document.createElement("div");
|
|
||||||
outer.className = "addon-item list-item";
|
|
||||||
outer.setAttribute("role", "button");
|
|
||||||
outer.addEventListener(
|
|
||||||
"click",
|
|
||||||
function(event) {
|
|
||||||
try {
|
|
||||||
openLink(
|
|
||||||
Services.urlFormatter.formatURLPref(
|
|
||||||
"extensions.getAddons.browseAddons"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
Cu.reportError(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
let img = document.createElement("img");
|
|
||||||
img.className = "icon";
|
|
||||||
img.setAttribute("src", AMO_ICON);
|
|
||||||
outer.appendChild(img);
|
|
||||||
|
|
||||||
let inner = document.createElement("div");
|
|
||||||
inner.className = "inner";
|
|
||||||
|
|
||||||
let title = document.createElement("div");
|
|
||||||
title.id = "browse-title";
|
|
||||||
title.className = "title";
|
|
||||||
title.textContent = this._getAmoTitle();
|
|
||||||
inner.appendChild(title);
|
|
||||||
|
|
||||||
outer.appendChild(inner);
|
|
||||||
return outer;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Ensure we get a localized string by using the previous title as a fallback
|
|
||||||
// if the new one has not yet been translated.
|
|
||||||
_getAmoTitle: function _getAmoTitle() {
|
|
||||||
const initialTitleUS = "Browse all Firefox Add-ons";
|
|
||||||
const updatedTitleUS = "Browse Firefox’s Recommended Extensions";
|
|
||||||
const initialTitleLocalized = gStringBundle.GetStringFromName(
|
|
||||||
"addons.browseAll"
|
|
||||||
);
|
|
||||||
const updatedTitleLocalized = gStringBundle.GetStringFromName(
|
|
||||||
"addons.browseRecommended"
|
|
||||||
);
|
|
||||||
let title = initialTitleLocalized;
|
|
||||||
|
|
||||||
const titleWasLocalized = updatedTitleLocalized !== updatedTitleUS;
|
|
||||||
const localeIsDefaultUS =
|
|
||||||
updatedTitleLocalized === updatedTitleUS &&
|
|
||||||
initialTitleLocalized === initialTitleUS;
|
|
||||||
|
|
||||||
if (titleWasLocalized || localeIsDefaultUS) {
|
|
||||||
title = updatedTitleLocalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
EventDispatcher.instance.dispatch("about:addons", { amoTitle: title });
|
|
||||||
return title;
|
|
||||||
},
|
|
||||||
|
|
||||||
_createItemForAddon: function _createItemForAddon(aAddon) {
|
|
||||||
let opType = this._getOpTypeForOperations(aAddon.pendingOperations);
|
|
||||||
let hasUpdate = this._addonHasUpdate(aAddon);
|
|
||||||
|
|
||||||
let optionsURL = aAddon.optionsURL || "";
|
|
||||||
|
|
||||||
let blocked = "";
|
|
||||||
switch (aAddon.blocklistState) {
|
|
||||||
case Ci.nsIBlocklistService.STATE_BLOCKED:
|
|
||||||
blocked = "blocked";
|
|
||||||
break;
|
|
||||||
case Ci.nsIBlocklistService.STATE_SOFTBLOCKED:
|
|
||||||
blocked = "softBlocked";
|
|
||||||
break;
|
|
||||||
case Ci.nsIBlocklistService.STATE_OUTDATED:
|
|
||||||
blocked = "outdated";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let item = this._createItem(aAddon);
|
|
||||||
item.setAttribute("isDisabled", !aAddon.isActive);
|
|
||||||
item.setAttribute(
|
|
||||||
"isUnsigned",
|
|
||||||
aAddon.signedState <= AddonManager.SIGNEDSTATE_MISSING
|
|
||||||
);
|
|
||||||
item.setAttribute("opType", opType);
|
|
||||||
if (blocked) {
|
|
||||||
item.setAttribute("blockedStatus", blocked);
|
|
||||||
}
|
|
||||||
item.setAttribute("optionsURL", optionsURL);
|
|
||||||
item.setAttribute("hasUpdate", hasUpdate);
|
|
||||||
item.addon = aAddon;
|
|
||||||
|
|
||||||
return item;
|
|
||||||
},
|
|
||||||
|
|
||||||
_getElementForAddon: function(aKey) {
|
|
||||||
let list = document.getElementById("addons-list");
|
|
||||||
let element = list.querySelector('div[addonID="' + CSS.escape(aKey) + '"]');
|
|
||||||
return element;
|
|
||||||
},
|
|
||||||
|
|
||||||
_addonHasUpdate(addon) {
|
|
||||||
return gChromeWin.ExtensionPermissions.updates.has(addon.id);
|
|
||||||
},
|
|
||||||
|
|
||||||
init: async function init() {
|
|
||||||
const aAddons = await AddonManager.getAllAddons();
|
|
||||||
|
|
||||||
// Clear all content before filling the addons
|
|
||||||
let list = document.getElementById("addons-list");
|
|
||||||
list.innerHTML = "";
|
|
||||||
|
|
||||||
aAddons.sort(function(a, b) {
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = 0; i < aAddons.length; i++) {
|
|
||||||
// Don't create item for system add-ons.
|
|
||||||
if (aAddons[i].isSystem) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let item = this._createItemForAddon(aAddons[i]);
|
|
||||||
list.appendChild(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a "Browse all Firefox Add-ons" item to the bottom of the list.
|
|
||||||
let browseItem = this._createBrowseItem();
|
|
||||||
list.appendChild(browseItem);
|
|
||||||
|
|
||||||
document
|
|
||||||
.getElementById("update-btn")
|
|
||||||
.addEventListener("click", Addons.updateCurrent.bind(this));
|
|
||||||
document
|
|
||||||
.getElementById("uninstall-btn")
|
|
||||||
.addEventListener("click", Addons.uninstallCurrent.bind(this));
|
|
||||||
document
|
|
||||||
.getElementById("cancel-btn")
|
|
||||||
.addEventListener("click", Addons.cancelUninstall.bind(this));
|
|
||||||
document
|
|
||||||
.getElementById("disable-btn")
|
|
||||||
.addEventListener("click", Addons.disable.bind(this));
|
|
||||||
document
|
|
||||||
.getElementById("enable-btn")
|
|
||||||
.addEventListener("click", Addons.enable.bind(this));
|
|
||||||
|
|
||||||
document
|
|
||||||
.getElementById("unsigned-learn-more")
|
|
||||||
.addEventListener("click", function() {
|
|
||||||
openLink(
|
|
||||||
Services.urlFormatter.formatURLPref("app.support.baseURL") +
|
|
||||||
"unsigned-addons"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_getOpTypeForOperations: function _getOpTypeForOperations(aOperations) {
|
|
||||||
if (aOperations & AddonManager.PENDING_UNINSTALL) {
|
|
||||||
return "needs-uninstall";
|
|
||||||
}
|
|
||||||
if (aOperations & AddonManager.PENDING_ENABLE) {
|
|
||||||
return "needs-enable";
|
|
||||||
}
|
|
||||||
if (aOperations & AddonManager.PENDING_DISABLE) {
|
|
||||||
return "needs-disable";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
|
|
||||||
showDetails: function showDetails(aListItem) {
|
|
||||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
|
||||||
detailItem.setAttribute("isDisabled", aListItem.getAttribute("isDisabled"));
|
|
||||||
detailItem.setAttribute("isUnsigned", aListItem.getAttribute("isUnsigned"));
|
|
||||||
detailItem.setAttribute("opType", aListItem.getAttribute("opType"));
|
|
||||||
detailItem.setAttribute("optionsURL", aListItem.getAttribute("optionsURL"));
|
|
||||||
let addon = (detailItem.addon = aListItem.addon);
|
|
||||||
|
|
||||||
let favicon = document.querySelector("#addons-details > .addon-item .icon");
|
|
||||||
favicon.setAttribute("src", addon.iconURL || AMO_ICON);
|
|
||||||
|
|
||||||
detailItem.querySelector(".title").textContent = addon.name;
|
|
||||||
detailItem.querySelector(".version").textContent = addon.version;
|
|
||||||
detailItem.querySelector(".description-full").textContent =
|
|
||||||
addon.description;
|
|
||||||
detailItem.querySelector(
|
|
||||||
".status-uninstalled"
|
|
||||||
).textContent = gStringBundle.formatStringFromName(
|
|
||||||
"addonStatus.uninstalled",
|
|
||||||
[addon.name]
|
|
||||||
);
|
|
||||||
|
|
||||||
let updateBtn = document.getElementById("update-btn");
|
|
||||||
if (this._addonHasUpdate(addon)) {
|
|
||||||
updateBtn.removeAttribute("hidden");
|
|
||||||
} else {
|
|
||||||
updateBtn.setAttribute("hidden", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let enableBtn = document.getElementById("enable-btn");
|
|
||||||
if (addon.appDisabled) {
|
|
||||||
enableBtn.setAttribute("disabled", "true");
|
|
||||||
} else {
|
|
||||||
enableBtn.removeAttribute("disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
let uninstallBtn = document.getElementById("uninstall-btn");
|
|
||||||
if (addon.scope == AddonManager.SCOPE_APPLICATION) {
|
|
||||||
uninstallBtn.setAttribute("disabled", "true");
|
|
||||||
} else {
|
|
||||||
uninstallBtn.removeAttribute("disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
let addonItem = document.querySelector("#addons-details > .addon-item");
|
|
||||||
let optionsBox = addonItem.querySelector(".options-box");
|
|
||||||
let optionsURL = aListItem.getAttribute("optionsURL");
|
|
||||||
|
|
||||||
// Always clean the options content before rendering the options of the
|
|
||||||
// newly selected extension.
|
|
||||||
optionsBox.innerHTML = "";
|
|
||||||
|
|
||||||
switch (parseInt(addon.optionsType)) {
|
|
||||||
case AddonManager.OPTIONS_TYPE_INLINE_BROWSER:
|
|
||||||
// Allow the options to use all the available width space.
|
|
||||||
optionsBox.classList.remove("inner");
|
|
||||||
|
|
||||||
this.createWebExtensionOptions(optionsBox, addon, addonItem);
|
|
||||||
break;
|
|
||||||
case AddonManager.OPTIONS_TYPE_TAB:
|
|
||||||
// Keep the usual layout for any options related the legacy (or system) add-ons
|
|
||||||
// when the options are opened in a new tab from a single button in the addon
|
|
||||||
// details page.
|
|
||||||
optionsBox.classList.add("inner");
|
|
||||||
|
|
||||||
this.createOptionsInTabButton(optionsBox, addon, addonItem);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
showAddonOptions();
|
|
||||||
},
|
|
||||||
|
|
||||||
createOptionsInTabButton: function(destination, addon, detailItem) {
|
|
||||||
let frame = destination.querySelector("iframe#addon-options");
|
|
||||||
let button = destination.querySelector("button#open-addon-options");
|
|
||||||
|
|
||||||
if (frame) {
|
|
||||||
// Remove any existent options frame (e.g. when the addon updates
|
|
||||||
// contains the open_in_tab options for the first time).
|
|
||||||
|
|
||||||
frame.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!button) {
|
|
||||||
button = document.createElement("button");
|
|
||||||
button.setAttribute("id", "open-addon-options");
|
|
||||||
button.textContent = gStringBundle.GetStringFromName("addon.options");
|
|
||||||
destination.appendChild(button);
|
|
||||||
}
|
|
||||||
|
|
||||||
button.onclick = async () => {
|
|
||||||
if (addon.isWebExtension) {
|
|
||||||
// WebExtensions are loaded asynchronously and the optionsURL
|
|
||||||
// may not be available until the addon has been started.
|
|
||||||
await addon.startupPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { optionsURL } = addon;
|
|
||||||
openOptionsInTab(optionsURL);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ensure that the Addon Options are visible (the options box will be hidden if the optionsURL
|
|
||||||
// attribute is an empty string, which happens when a WebExtensions is still loading).
|
|
||||||
detailItem.removeAttribute("optionsURL");
|
|
||||||
},
|
|
||||||
|
|
||||||
createWebExtensionOptions: async function(destination, addon, detailItem) {
|
|
||||||
// WebExtensions are loaded asynchronously and the optionsURL
|
|
||||||
// may not be available until the addon has been started.
|
|
||||||
await addon.startupPromise;
|
|
||||||
|
|
||||||
const { optionsURL, optionsBrowserStyle } = addon;
|
|
||||||
let frame = destination.querySelector("iframe#addon-options");
|
|
||||||
|
|
||||||
if (!frame) {
|
|
||||||
let originalHeight;
|
|
||||||
frame = document.createElement("iframe");
|
|
||||||
frame.setAttribute("id", "addon-options");
|
|
||||||
frame.setAttribute("mozbrowser", "true");
|
|
||||||
frame.setAttribute("style", "width: 100%; overflow: hidden;");
|
|
||||||
|
|
||||||
// Adjust iframe height to the iframe content (also between navigation of multiple options
|
|
||||||
// files).
|
|
||||||
frame.onload = evt => {
|
|
||||||
if (evt.target !== frame) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { document } = frame.contentWindow;
|
|
||||||
const bodyScrollHeight = document.body && document.body.scrollHeight;
|
|
||||||
const documentScrollHeight = document.documentElement.scrollHeight;
|
|
||||||
|
|
||||||
// Set the iframe height to the maximum between the body and the document
|
|
||||||
// scrollHeight values.
|
|
||||||
frame.style.height =
|
|
||||||
Math.max(bodyScrollHeight, documentScrollHeight) + "px";
|
|
||||||
|
|
||||||
// Restore the original iframe height between option page loads,
|
|
||||||
// so that we don't force the new document to have the same size
|
|
||||||
// of the previosuly loaded option page.
|
|
||||||
frame.contentWindow.addEventListener(
|
|
||||||
"unload",
|
|
||||||
() => {
|
|
||||||
frame.style.height = originalHeight + "px";
|
|
||||||
},
|
|
||||||
{ once: true }
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
destination.appendChild(frame);
|
|
||||||
originalHeight = frame.getBoundingClientRect().height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loading the URL this way prevents the native back
|
|
||||||
// button from applying to the iframe.
|
|
||||||
frame.contentWindow.location.replace(optionsURL);
|
|
||||||
|
|
||||||
// Ensure that the Addon Options are visible (the options box will be hidden if the optionsURL
|
|
||||||
// attribute is an empty string, which happens when a WebExtensions is still loading).
|
|
||||||
detailItem.removeAttribute("optionsURL");
|
|
||||||
},
|
|
||||||
|
|
||||||
setEnabled: function setEnabled(aValue, aAddon) {
|
|
||||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
|
||||||
let addon = aAddon || detailItem.addon;
|
|
||||||
if (!addon) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let listItem = this._getElementForAddon(addon.id);
|
|
||||||
|
|
||||||
function setDisabled(addon, value) {
|
|
||||||
if (value) {
|
|
||||||
return addon.disable();
|
|
||||||
}
|
|
||||||
return addon.enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateOtherThemeStateInUI(item) {
|
|
||||||
if (aValue) {
|
|
||||||
// Mark the previously enabled theme as disabled.
|
|
||||||
if (item.addon.isActive) {
|
|
||||||
item.setAttribute("isDisabled", true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// The current theme is being disabled - enable the default theme.
|
|
||||||
} else if (item.addon.id == "default-theme@mozilla.org") {
|
|
||||||
item.removeAttribute("isDisabled");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let opType;
|
|
||||||
if (addon.type == "theme") {
|
|
||||||
// Themes take care of themselves to make sure only one is active at the
|
|
||||||
// same time, but we need to fix up the state of other themes in the UI.
|
|
||||||
let list = document.getElementById("addons-list");
|
|
||||||
let item = list.firstElementChild;
|
|
||||||
while (item) {
|
|
||||||
if (
|
|
||||||
item.addon &&
|
|
||||||
item.addon.type == "theme" &&
|
|
||||||
updateOtherThemeStateInUI(item)
|
|
||||||
) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
item = item.nextSibling;
|
|
||||||
}
|
|
||||||
setDisabled(addon, !aValue);
|
|
||||||
} else if (addon.type == "locale") {
|
|
||||||
setDisabled(addon, !aValue);
|
|
||||||
} else {
|
|
||||||
setDisabled(addon, !aValue);
|
|
||||||
opType = this._getOpTypeForOperations(addon.pendingOperations);
|
|
||||||
|
|
||||||
if (
|
|
||||||
addon.pendingOperations & AddonManager.PENDING_ENABLE ||
|
|
||||||
addon.pendingOperations & AddonManager.PENDING_DISABLE
|
|
||||||
) {
|
|
||||||
this.showRestart();
|
|
||||||
} else if (
|
|
||||||
listItem &&
|
|
||||||
/needs-(enable|disable)/.test(listItem.getAttribute("opType"))
|
|
||||||
) {
|
|
||||||
this.hideRestart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addon == detailItem.addon) {
|
|
||||||
detailItem.setAttribute("isDisabled", !aValue);
|
|
||||||
if (opType) {
|
|
||||||
detailItem.setAttribute("opType", opType);
|
|
||||||
} else {
|
|
||||||
detailItem.removeAttribute("opType");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove any addon options iframe if the currently selected addon has been disabled.
|
|
||||||
if (!aValue) {
|
|
||||||
const addonOptionsIframe = document.querySelector("#addon-options");
|
|
||||||
if (addonOptionsIframe) {
|
|
||||||
addonOptionsIframe.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync to the list item
|
|
||||||
if (listItem) {
|
|
||||||
listItem.setAttribute("isDisabled", !aValue);
|
|
||||||
if (opType) {
|
|
||||||
listItem.setAttribute("opType", opType);
|
|
||||||
} else {
|
|
||||||
listItem.removeAttribute("opType");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
enable: function enable() {
|
|
||||||
this.setEnabled(true);
|
|
||||||
},
|
|
||||||
|
|
||||||
disable: function disable() {
|
|
||||||
this.setEnabled(false);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateCurrent() {
|
|
||||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
|
||||||
|
|
||||||
let addon = detailItem.addon;
|
|
||||||
if (!addon) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gChromeWin.ExtensionPermissions.applyUpdate(addon.id);
|
|
||||||
},
|
|
||||||
|
|
||||||
uninstallCurrent: function uninstallCurrent() {
|
|
||||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
|
||||||
|
|
||||||
let addon = detailItem.addon;
|
|
||||||
if (!addon) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.uninstall(addon);
|
|
||||||
},
|
|
||||||
|
|
||||||
uninstall: function uninstall(aAddon) {
|
|
||||||
if (!aAddon) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let listItem = this._getElementForAddon(aAddon.id);
|
|
||||||
aAddon.uninstall();
|
|
||||||
|
|
||||||
if (aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL) {
|
|
||||||
this.showRestart();
|
|
||||||
|
|
||||||
// A disabled addon doesn't need a restart so it has no pending ops and
|
|
||||||
// can't be cancelled
|
|
||||||
let opType = this._getOpTypeForOperations(aAddon.pendingOperations);
|
|
||||||
if (!aAddon.isActive && opType == "") {
|
|
||||||
opType = "needs-uninstall";
|
|
||||||
}
|
|
||||||
|
|
||||||
detailItem.setAttribute("opType", opType);
|
|
||||||
listItem.setAttribute("opType", opType);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelUninstall: function ev_cancelUninstall() {
|
|
||||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
|
||||||
let addon = detailItem.addon;
|
|
||||||
if (!addon) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
addon.cancelUninstall();
|
|
||||||
this.hideRestart();
|
|
||||||
|
|
||||||
let opType = this._getOpTypeForOperations(addon.pendingOperations);
|
|
||||||
detailItem.setAttribute("opType", opType);
|
|
||||||
|
|
||||||
let listItem = this._getElementForAddon(addon.id);
|
|
||||||
listItem.setAttribute("opType", opType);
|
|
||||||
},
|
|
||||||
|
|
||||||
showRestart: function showRestart() {
|
|
||||||
this._restartCount++;
|
|
||||||
gChromeWin.XPInstallObserver.showRestartPrompt();
|
|
||||||
},
|
|
||||||
|
|
||||||
hideRestart: function hideRestart() {
|
|
||||||
this._restartCount--;
|
|
||||||
if (this._restartCount == 0) {
|
|
||||||
gChromeWin.XPInstallObserver.hideRestartPrompt();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onEnabled: function(aAddon) {
|
|
||||||
let listItem = this._getElementForAddon(aAddon.id);
|
|
||||||
if (!listItem) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload the details to pick up any options now that it's enabled.
|
|
||||||
listItem.setAttribute("optionsURL", aAddon.optionsURL || "");
|
|
||||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
|
||||||
if (aAddon == detailItem.addon) {
|
|
||||||
this.showDetails(listItem);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onInstallEnded: function(aInstall, aAddon) {
|
|
||||||
let needsRestart = false;
|
|
||||||
if (
|
|
||||||
aInstall.existingAddon &&
|
|
||||||
aInstall.existingAddon.pendingOperations & AddonManager.PENDING_UPGRADE
|
|
||||||
) {
|
|
||||||
needsRestart = true;
|
|
||||||
} else if (aAddon.pendingOperations & AddonManager.PENDING_INSTALL) {
|
|
||||||
needsRestart = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let list = document.getElementById("addons-list");
|
|
||||||
let element = this._getElementForAddon(aAddon.id);
|
|
||||||
if (!element) {
|
|
||||||
element = this._createItemForAddon(aAddon);
|
|
||||||
list.insertBefore(element, list.firstElementChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsRestart) {
|
|
||||||
element.setAttribute("opType", "needs-restart");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onInstalled: function(aAddon) {
|
|
||||||
let list = document.getElementById("addons-list");
|
|
||||||
let element = this._getElementForAddon(aAddon.id);
|
|
||||||
if (element) {
|
|
||||||
// Upgrade of an existing addon, update version and description in
|
|
||||||
// list item and detail view, plus indicators about a pending update.
|
|
||||||
element.querySelector(".version").textContent = aAddon.version;
|
|
||||||
|
|
||||||
let desc = element.querySelector(".description");
|
|
||||||
if (desc) {
|
|
||||||
desc.textContent = aAddon.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
element.setAttribute("hasUpdate", false);
|
|
||||||
document.getElementById("update-btn").setAttribute("hidden", true);
|
|
||||||
|
|
||||||
element = document.querySelector("#addons-details > .addon-item");
|
|
||||||
if (element.addon && element.addon.id == aAddon.id) {
|
|
||||||
element.querySelector(".version").textContent = aAddon.version;
|
|
||||||
element.querySelector(".description-full").textContent =
|
|
||||||
aAddon.description;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
element = this._createItemForAddon(aAddon);
|
|
||||||
|
|
||||||
// Themes aren't considered active on install, so set existing as disabled, and new one enabled.
|
|
||||||
if (aAddon.type == "theme") {
|
|
||||||
let item = list.firstElementChild;
|
|
||||||
while (item) {
|
|
||||||
if (item.addon && item.addon.type == "theme") {
|
|
||||||
item.setAttribute("isDisabled", true);
|
|
||||||
}
|
|
||||||
item = item.nextSibling;
|
|
||||||
}
|
|
||||||
element.setAttribute("isDisabled", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
list.insertBefore(element, list.firstElementChild);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onUninstalled: function(aAddon) {
|
|
||||||
let list = document.getElementById("addons-list");
|
|
||||||
let element = this._getElementForAddon(aAddon.id);
|
|
||||||
list.removeChild(element);
|
|
||||||
|
|
||||||
// Go back if we're in the detail view of the add-on that was uninstalled.
|
|
||||||
let detailItem = document.querySelector("#addons-details > .addon-item");
|
|
||||||
if (detailItem.addon.id == aAddon.id) {
|
|
||||||
history.back();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onInstallFailed: function(aInstall) {},
|
|
||||||
|
|
||||||
onDownloadProgress: function xpidm_onDownloadProgress(aInstall) {},
|
|
||||||
|
|
||||||
onDownloadFailed: function(aInstall) {},
|
|
||||||
|
|
||||||
onDownloadCancelled: function(aInstall) {},
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("load", init);
|
|
||||||
window.addEventListener("unload", uninit);
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
|
||||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
|
|
||||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
|
||||||
%brandDTD;
|
|
||||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
|
|
||||||
%globalDTD;
|
|
||||||
<!ENTITY % aboutDTD SYSTEM "chrome://browser/locale/aboutAddons.dtd" >
|
|
||||||
%aboutDTD;
|
|
||||||
]>
|
|
||||||
|
|
||||||
<!-- 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/. -->
|
|
||||||
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<title>&aboutAddons.title2;</title>
|
|
||||||
<meta name="viewport" content="width=device-width; user-scalable=0" />
|
|
||||||
<link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/>
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/aboutAddons.css" type="text/css"/>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body dir="&locale.dir;">
|
|
||||||
<menu type="context" id="addonmenu">
|
|
||||||
<menuitem id="contextmenu-enable" label="&addonAction.enable;"></menuitem>
|
|
||||||
<menuitem id="contextmenu-disable" label="&addonAction.disable;" ></menuitem>
|
|
||||||
<menuitem id="contextmenu-uninstall" label="&addonAction.uninstall;" ></menuitem>
|
|
||||||
</menu>
|
|
||||||
|
|
||||||
<div id="addons-header" class="header">
|
|
||||||
<div>&aboutAddons.header2;</div>
|
|
||||||
</div>
|
|
||||||
<div id="addons-list" class="list hidden">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="addons-details" class="list hidden">
|
|
||||||
<div class="addon-item list-item">
|
|
||||||
<img class="icon"/>
|
|
||||||
<div class="inner">
|
|
||||||
<div class="details">
|
|
||||||
<div class="title"></div><div class="version"></div>
|
|
||||||
</div>
|
|
||||||
<div class="description-full"></div>
|
|
||||||
</div>
|
|
||||||
<div class="warn-unsigned">&addonUnsigned.message; <a id="unsigned-learn-more">&addonUnsigned.learnMore;</a></div>
|
|
||||||
<div class="options-box"></div>
|
|
||||||
<div class="status status-uninstalled show-on-uninstall"></div>
|
|
||||||
<div class="buttons">
|
|
||||||
<button id="update-btn" class="show-on-update">&addonAction.update;</button>
|
|
||||||
<button id="enable-btn" class="show-on-disable hide-on-enable hide-on-uninstall" >&addonAction.enable;</button>
|
|
||||||
<button id="disable-btn" class="show-on-enable hide-on-disable hide-on-uninstall" >&addonAction.disable;</button>
|
|
||||||
<button id="uninstall-btn" class="hide-on-uninstall" >&addonAction.uninstall;</button>
|
|
||||||
<button id="cancel-btn" class="show-on-uninstall" >&addonAction.undo;</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="application/javascript" src="chrome://browser/content/aboutAddons.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,152 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<!DOCTYPE html [
|
|
||||||
<!ENTITY % htmlDTD
|
|
||||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
|
||||||
"DTD/xhtml1-strict.dtd">
|
|
||||||
%htmlDTD;
|
|
||||||
<!ENTITY % globalDTD
|
|
||||||
SYSTEM "chrome://global/locale/global.dtd">
|
|
||||||
%globalDTD;
|
|
||||||
<!ENTITY % certerrorDTD
|
|
||||||
SYSTEM "chrome://browser/locale/aboutCertError.dtd">
|
|
||||||
%certerrorDTD;
|
|
||||||
]>
|
|
||||||
|
|
||||||
<!-- 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/. -->
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<title>&certerror.pagetitle;</title>
|
|
||||||
<meta name="viewport" content="width=device-width; user-scalable=false" />
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/netError.css" media="all" />
|
|
||||||
<!-- This page currently uses the same favicon as neterror.xhtml.
|
|
||||||
If the location of the favicon is changed for both pages, the
|
|
||||||
FAVICON_ERRORPAGE_URL symbol in toolkit/components/places/src/nsFaviconService.h
|
|
||||||
should be updated. If this page starts using a different favicon
|
|
||||||
than neterrorm nsFaviconService->SetAndLoadFaviconForPage
|
|
||||||
should be updated to ignore this one as well. -->
|
|
||||||
<link rel="icon" type="image/png" id="favicon" sizes="64x64" href="chrome://browser/skin/images/certerror-warning.png"/>
|
|
||||||
|
|
||||||
<script type="application/javascript"><![CDATA[
|
|
||||||
// Error url MUST be formatted like this:
|
|
||||||
// about:certerror?e=error&u=url&d=desc
|
|
||||||
|
|
||||||
// Note that this file uses document.documentURI to get
|
|
||||||
// the URL (with the format from above). This is because
|
|
||||||
// document.location.href gets the current URI off the docshell,
|
|
||||||
// which is the URL displayed in the location bar, i.e.
|
|
||||||
// the URI that the user attempted to load.
|
|
||||||
|
|
||||||
function getCSSClass() {
|
|
||||||
var url = document.documentURI;
|
|
||||||
var matches = url.match(/s\=([^&]+)\&/);
|
|
||||||
// s is optional, if no match just return nothing
|
|
||||||
if (!matches || matches.length < 2)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
// parenthetical match is the second entry
|
|
||||||
return decodeURIComponent(matches[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initPage() {
|
|
||||||
// Replace the "#1" string in the intro with the hostname. Trickier
|
|
||||||
// than it might seem since we want to preserve the <b> tags, but
|
|
||||||
// not allow for any injection by just using innerHTML. Instead,
|
|
||||||
// just find the right target text node.
|
|
||||||
var intro = document.getElementById("introContentP1");
|
|
||||||
function replaceWithHost(node) {
|
|
||||||
if (node.textContent == "#1")
|
|
||||||
node.textContent = location.host;
|
|
||||||
else
|
|
||||||
for (var i = 0; i < node.childNodes.length; i++)
|
|
||||||
replaceWithHost(node.childNodes[i]);
|
|
||||||
}
|
|
||||||
replaceWithHost(intro);
|
|
||||||
|
|
||||||
if (getCSSClass() == "expertBadCert") {
|
|
||||||
toggle("technicalContent");
|
|
||||||
toggle("expertContent");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disallow overrides if this is a Strict-Transport-Security
|
|
||||||
// host and the cert is bad (STS Spec section 7.3) or if the
|
|
||||||
// certerror is in a frame (bug 633691).
|
|
||||||
if (getCSSClass() == "badStsCert" || window != top)
|
|
||||||
document.getElementById("expertContent").setAttribute("hidden", "true");
|
|
||||||
|
|
||||||
var event = new CustomEvent("AboutCertErrorLoad", {bubbles: true});
|
|
||||||
document.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createLink(el, id, text) {
|
|
||||||
var anchorEl = document.createElement("a");
|
|
||||||
anchorEl.setAttribute("id", id);
|
|
||||||
anchorEl.setAttribute("title", text);
|
|
||||||
anchorEl.appendChild(document.createTextNode(text));
|
|
||||||
el.appendChild(anchorEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle(id) {
|
|
||||||
var el = document.getElementById(id);
|
|
||||||
if (el.hasAttribute("collapsed"))
|
|
||||||
el.removeAttribute("collapsed");
|
|
||||||
else
|
|
||||||
el.setAttribute("collapsed", true);
|
|
||||||
}
|
|
||||||
]]></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body id="errorPage" class="certerror" dir="&locale.dir;">
|
|
||||||
|
|
||||||
<!-- PAGE CONTAINER (for styling purposes only) -->
|
|
||||||
<div id="errorPageContainer">
|
|
||||||
|
|
||||||
<!-- Error Title -->
|
|
||||||
<div id="errorTitle">
|
|
||||||
<h1 class="errorTitleText">&certerror.longpagetitle;</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- LONG CONTENT (the section most likely to require scrolling) -->
|
|
||||||
<div id="errorLongContent">
|
|
||||||
<div id="introContent">
|
|
||||||
<p id="introContentP1">&certerror.introPara1;</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="whatShouldIDoContent">
|
|
||||||
<h2>&certerror.whatShouldIDo.heading;</h2>
|
|
||||||
<div>
|
|
||||||
<p id="whatShouldIDoContentText">&certerror.whatShouldIDo.content;</p>
|
|
||||||
<button id="getMeOutOfHereButton">&certerror.getMeOutOfHere.label;</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- The following sections can be unhidden by default by setting the
|
|
||||||
"browser.xul.error_pages.expert_bad_cert" pref to true -->
|
|
||||||
<div id="technicalContent" collapsed="true">
|
|
||||||
<h2 class="expander" onclick="toggle('technicalContent');" id="technicalContentHeading">&certerror.technical.heading;</h2>
|
|
||||||
<p id="technicalContentText"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="expertContent" collapsed="true">
|
|
||||||
<h2 class="expander" onclick="toggle('expertContent');" id="expertContentHeading">&certerror.expert.heading;</h2>
|
|
||||||
<div>
|
|
||||||
<p>&certerror.expert.content;</p>
|
|
||||||
<p>&certerror.expert.contentPara2;</p>
|
|
||||||
<button id="temporaryExceptionButton">&certerror.addTemporaryException.label;</button>
|
|
||||||
<button id="permanentExceptionButton">&certerror.addPermanentException.label;</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
- Note: It is important to run the script this way, instead of using
|
|
||||||
- an onload handler. This is because error pages are loaded as
|
|
||||||
- LOAD_BACKGROUND, which means that onload handlers will not be executed.
|
|
||||||
-->
|
|
||||||
<script type="application/javascript">initPage();</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,427 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"Downloads",
|
|
||||||
"resource://gre/modules/Downloads.jsm"
|
|
||||||
);
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"DownloadUtils",
|
|
||||||
"resource://gre/modules/DownloadUtils.jsm"
|
|
||||||
);
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"EventDispatcher",
|
|
||||||
"resource://gre/modules/Messaging.jsm"
|
|
||||||
);
|
|
||||||
ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"PluralForm",
|
|
||||||
"resource://gre/modules/PluralForm.jsm"
|
|
||||||
);
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"Services",
|
|
||||||
"resource://gre/modules/Services.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
var gStrings = Services.strings.createBundle(
|
|
||||||
"chrome://browser/locale/aboutDownloads.properties"
|
|
||||||
);
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "strings", () =>
|
|
||||||
Services.strings.createBundle(
|
|
||||||
"chrome://browser/locale/aboutDownloads.properties"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
function deleteDownload(download) {
|
|
||||||
download.finalize(true).catch(Cu.reportError);
|
|
||||||
OS.File.remove(download.target.path).catch(ex => {
|
|
||||||
if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) {
|
|
||||||
Cu.reportError(ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var contextMenu = {
|
|
||||||
_items: [],
|
|
||||||
_targetDownload: null,
|
|
||||||
|
|
||||||
init: function() {
|
|
||||||
let element = document.getElementById("downloadmenu");
|
|
||||||
element.addEventListener(
|
|
||||||
"click",
|
|
||||||
event => (event.download = this._targetDownload),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
this._items = [
|
|
||||||
new ContextMenuItem(
|
|
||||||
"open",
|
|
||||||
download => download.succeeded,
|
|
||||||
download => download.launch().catch(Cu.reportError)
|
|
||||||
),
|
|
||||||
new ContextMenuItem(
|
|
||||||
"retry",
|
|
||||||
download =>
|
|
||||||
download.error || (download.canceled && !download.hasPartialData),
|
|
||||||
download => download.start().catch(Cu.reportError)
|
|
||||||
),
|
|
||||||
new ContextMenuItem(
|
|
||||||
"remove",
|
|
||||||
download => download.stopped,
|
|
||||||
download => {
|
|
||||||
Downloads.getList(Downloads.ALL)
|
|
||||||
.then(list => list.remove(download))
|
|
||||||
.catch(Cu.reportError);
|
|
||||||
deleteDownload(download);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
new ContextMenuItem(
|
|
||||||
"pause",
|
|
||||||
download => !download.stopped && download.hasPartialData,
|
|
||||||
download => download.cancel().catch(Cu.reportError)
|
|
||||||
),
|
|
||||||
new ContextMenuItem(
|
|
||||||
"resume",
|
|
||||||
download => download.canceled && download.hasPartialData,
|
|
||||||
download => download.start().catch(Cu.reportError)
|
|
||||||
),
|
|
||||||
new ContextMenuItem(
|
|
||||||
"cancel",
|
|
||||||
download =>
|
|
||||||
!download.stopped || (download.canceled && download.hasPartialData),
|
|
||||||
download => {
|
|
||||||
download.cancel().catch(Cu.reportError);
|
|
||||||
download.removePartialData().catch(Cu.reportError);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
// following menu item is a global action
|
|
||||||
new ContextMenuItem(
|
|
||||||
"removeall",
|
|
||||||
() => downloadLists.finished.length > 0,
|
|
||||||
() => downloadLists.removeFinished()
|
|
||||||
),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
addContextMenuEventListener: function(element) {
|
|
||||||
element.addEventListener("contextmenu", this.onContextMenu.bind(this));
|
|
||||||
},
|
|
||||||
|
|
||||||
onContextMenu: function(event) {
|
|
||||||
let target = event.target;
|
|
||||||
while (target && !target.download) {
|
|
||||||
target = target.parentNode;
|
|
||||||
}
|
|
||||||
if (!target) {
|
|
||||||
Cu.reportError("No download found for context menu target");
|
|
||||||
event.preventDefault();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// capture the target download for menu items to use in a click event
|
|
||||||
this._targetDownload = target.download;
|
|
||||||
for (let item of this._items) {
|
|
||||||
item.updateVisibility(target.download);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function ContextMenuItem(name, isVisible, action) {
|
|
||||||
this.element = document.getElementById("contextmenu-" + name);
|
|
||||||
this.isVisible = isVisible;
|
|
||||||
|
|
||||||
this.element.addEventListener("click", event => action(event.download));
|
|
||||||
}
|
|
||||||
|
|
||||||
ContextMenuItem.prototype = {
|
|
||||||
updateVisibility: function(download) {
|
|
||||||
this.element.hidden = !this.isVisible(download);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function DownloadListView(type, listElementId) {
|
|
||||||
this.listElement = document.getElementById(listElementId);
|
|
||||||
contextMenu.addContextMenuEventListener(this.listElement);
|
|
||||||
|
|
||||||
this.items = new Map();
|
|
||||||
|
|
||||||
Downloads.getList(type)
|
|
||||||
.then(list => list.addView(this))
|
|
||||||
.catch(Cu.reportError);
|
|
||||||
|
|
||||||
window.addEventListener("unload", event => {
|
|
||||||
Downloads.getList(type)
|
|
||||||
.then(list => list.removeView(this))
|
|
||||||
.catch(Cu.reportError);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
DownloadListView.prototype = {
|
|
||||||
get finished() {
|
|
||||||
let finished = [];
|
|
||||||
for (let download of this.items.keys()) {
|
|
||||||
if (download.stopped && (!download.hasPartialData || download.error)) {
|
|
||||||
finished.push(download);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return finished;
|
|
||||||
},
|
|
||||||
|
|
||||||
insertOrMoveItem: function(item) {
|
|
||||||
var compare = (a, b) => {
|
|
||||||
// active downloads always before stopped downloads
|
|
||||||
if (a.stopped != b.stopped) {
|
|
||||||
return b.stopped ? -1 : 1;
|
|
||||||
}
|
|
||||||
// most recent downloads first
|
|
||||||
return b.startTime - a.startTime;
|
|
||||||
};
|
|
||||||
|
|
||||||
let insertLocation = this.listElement.firstChild;
|
|
||||||
while (
|
|
||||||
insertLocation &&
|
|
||||||
compare(item.download, insertLocation.download) > 0
|
|
||||||
) {
|
|
||||||
insertLocation = insertLocation.nextElementSibling;
|
|
||||||
}
|
|
||||||
this.listElement.insertBefore(item.element, insertLocation);
|
|
||||||
},
|
|
||||||
|
|
||||||
onDownloadAdded: function(download) {
|
|
||||||
let item = new DownloadItem(download);
|
|
||||||
this.items.set(download, item);
|
|
||||||
this.insertOrMoveItem(item);
|
|
||||||
},
|
|
||||||
|
|
||||||
onDownloadChanged: function(download) {
|
|
||||||
let item = this.items.get(download);
|
|
||||||
if (!item) {
|
|
||||||
Cu.reportError("No DownloadItem found for download");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.stateChanged) {
|
|
||||||
this.insertOrMoveItem(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
item.onDownloadChanged();
|
|
||||||
},
|
|
||||||
|
|
||||||
onDownloadRemoved: function(download) {
|
|
||||||
let item = this.items.get(download);
|
|
||||||
if (!item) {
|
|
||||||
Cu.reportError("No DownloadItem found for download");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.items.delete(download);
|
|
||||||
this.listElement.removeChild(item.element);
|
|
||||||
|
|
||||||
EventDispatcher.instance.sendRequest({
|
|
||||||
type: "Download:Remove",
|
|
||||||
path: download.target.path,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var downloadLists = {
|
|
||||||
init: function() {
|
|
||||||
this.publicDownloads = new DownloadListView(
|
|
||||||
Downloads.PUBLIC,
|
|
||||||
"public-downloads-list"
|
|
||||||
);
|
|
||||||
this.privateDownloads = new DownloadListView(
|
|
||||||
Downloads.PRIVATE,
|
|
||||||
"private-downloads-list"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
get finished() {
|
|
||||||
return this.publicDownloads.finished.concat(this.privateDownloads.finished);
|
|
||||||
},
|
|
||||||
|
|
||||||
removeFinished: function() {
|
|
||||||
let finished = this.finished;
|
|
||||||
if (finished.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let title = strings.GetStringFromName("downloadAction.deleteAll");
|
|
||||||
let messageForm = strings.GetStringFromName("downloadMessage.deleteAll");
|
|
||||||
let message = PluralForm.get(finished.length, messageForm).replace(
|
|
||||||
"#1",
|
|
||||||
finished.length
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Services.prompt.confirm(null, title, message)) {
|
|
||||||
Downloads.getList(Downloads.ALL).then(list => {
|
|
||||||
for (let download of finished) {
|
|
||||||
list.remove(download).catch(Cu.reportError);
|
|
||||||
deleteDownload(download);
|
|
||||||
}
|
|
||||||
}, Cu.reportError);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function DownloadItem(download) {
|
|
||||||
this._download = download;
|
|
||||||
this._updateFromDownload();
|
|
||||||
|
|
||||||
this._domain = DownloadUtils.getURIHost(download.source.url)[0];
|
|
||||||
this._fileName = this._htmlEscape(OS.Path.basename(download.target.path));
|
|
||||||
this._iconUrl = "moz-icon://" + this._fileName + "?size=64";
|
|
||||||
this._startDate = this._htmlEscape(
|
|
||||||
DownloadUtils.getReadableDates(download.startTime)[0]
|
|
||||||
);
|
|
||||||
|
|
||||||
this._element = this.createElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
const kDownloadStatePropertyNames = [
|
|
||||||
"stopped",
|
|
||||||
"succeeded",
|
|
||||||
"canceled",
|
|
||||||
"error",
|
|
||||||
"startTime",
|
|
||||||
];
|
|
||||||
|
|
||||||
DownloadItem.prototype = {
|
|
||||||
_htmlEscape: function(s) {
|
|
||||||
s = s.replace(/&/g, "&");
|
|
||||||
s = s.replace(/>/g, ">");
|
|
||||||
s = s.replace(/</g, "<");
|
|
||||||
s = s.replace(/"/g, """);
|
|
||||||
s = s.replace(/'/g, "'");
|
|
||||||
return s;
|
|
||||||
},
|
|
||||||
|
|
||||||
_updateFromDownload: function() {
|
|
||||||
this._state = {};
|
|
||||||
kDownloadStatePropertyNames.forEach(
|
|
||||||
name => (this._state[name] = this._download[name]),
|
|
||||||
this
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
get stateChanged() {
|
|
||||||
return kDownloadStatePropertyNames.some(
|
|
||||||
name => this._state[name] != this._download[name],
|
|
||||||
this
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
get download() {
|
|
||||||
return this._download;
|
|
||||||
},
|
|
||||||
get element() {
|
|
||||||
return this._element;
|
|
||||||
},
|
|
||||||
|
|
||||||
createElement: function() {
|
|
||||||
let template = document.getElementById("download-item");
|
|
||||||
// TODO: use this once <template> is working
|
|
||||||
// let element = document.importNode(template.content, true);
|
|
||||||
|
|
||||||
// simulate a <template> node...
|
|
||||||
let element = template.cloneNode(true);
|
|
||||||
element.removeAttribute("id");
|
|
||||||
element.removeAttribute("style");
|
|
||||||
|
|
||||||
// launch the download if clicked
|
|
||||||
element.addEventListener("click", this.onClick.bind(this));
|
|
||||||
|
|
||||||
// set download as an expando property for the context menu
|
|
||||||
element.download = this.download;
|
|
||||||
|
|
||||||
// fill in template placeholders
|
|
||||||
this.updateElement(element);
|
|
||||||
|
|
||||||
return element;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateElement: function(element) {
|
|
||||||
element.querySelector(".date").textContent = this.startDate;
|
|
||||||
element.querySelector(".domain").textContent = this.domain;
|
|
||||||
element.querySelector(".icon").src = this.iconUrl;
|
|
||||||
element.querySelector(".size").textContent = this.size;
|
|
||||||
element.querySelector(".state").textContent = this.stateDescription;
|
|
||||||
element.querySelector(".title").setAttribute("value", this.fileName);
|
|
||||||
},
|
|
||||||
|
|
||||||
onClick: function(event) {
|
|
||||||
if (this.download.succeeded) {
|
|
||||||
this.download.launch().catch(Cu.reportError);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onDownloadChanged: function() {
|
|
||||||
this._updateFromDownload();
|
|
||||||
this.updateElement(this.element);
|
|
||||||
},
|
|
||||||
|
|
||||||
// template properties below
|
|
||||||
get domain() {
|
|
||||||
return this._domain;
|
|
||||||
},
|
|
||||||
get fileName() {
|
|
||||||
return this._fileName;
|
|
||||||
},
|
|
||||||
get id() {
|
|
||||||
return this._id;
|
|
||||||
},
|
|
||||||
get iconUrl() {
|
|
||||||
return this._iconUrl;
|
|
||||||
},
|
|
||||||
|
|
||||||
get size() {
|
|
||||||
if (this.download.succeeded && this.download.target.exists) {
|
|
||||||
return DownloadUtils.convertByteUnits(this.download.target.size).join("");
|
|
||||||
} else if (this.download.hasProgress) {
|
|
||||||
return DownloadUtils.convertByteUnits(this.download.totalBytes).join("");
|
|
||||||
}
|
|
||||||
return strings.GetStringFromName("downloadState.unknownSize");
|
|
||||||
},
|
|
||||||
|
|
||||||
get startDate() {
|
|
||||||
return this._startDate;
|
|
||||||
},
|
|
||||||
|
|
||||||
get stateDescription() {
|
|
||||||
let name;
|
|
||||||
if (this.download.error) {
|
|
||||||
name = "downloadState.failed";
|
|
||||||
} else if (this.download.canceled) {
|
|
||||||
if (this.download.hasPartialData) {
|
|
||||||
name = "downloadState.paused";
|
|
||||||
} else {
|
|
||||||
name = "downloadState.canceled";
|
|
||||||
}
|
|
||||||
} else if (!this.download.stopped) {
|
|
||||||
if (this.download.currentBytes > 0) {
|
|
||||||
name = "downloadState.downloading";
|
|
||||||
} else {
|
|
||||||
name = "downloadState.starting";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name) {
|
|
||||||
return strings.GetStringFromName(name);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", event => {
|
|
||||||
contextMenu.init();
|
|
||||||
downloadLists.init();
|
|
||||||
});
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
|
||||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
|
|
||||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
|
||||||
%brandDTD;
|
|
||||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
|
|
||||||
%globalDTD;
|
|
||||||
<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/aboutDownloads.dtd" >
|
|
||||||
%downloadsDTD;
|
|
||||||
]>
|
|
||||||
|
|
||||||
<!-- 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/. -->
|
|
||||||
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
|
||||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
|
||||||
<head>
|
|
||||||
<title>&aboutDownloads.title;</title>
|
|
||||||
<meta name="viewport" content="width=device-width; user-scalable=0" />
|
|
||||||
<link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/>
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/aboutDownloads.css" type="text/css"/>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body dir="&locale.dir;">
|
|
||||||
<menu type="context" id="downloadmenu">
|
|
||||||
<menuitem id="contextmenu-open" label="&aboutDownloads.open;"></menuitem>
|
|
||||||
<menuitem id="contextmenu-retry" label="&aboutDownloads.retry;"></menuitem>
|
|
||||||
<menuitem id="contextmenu-remove" label="&aboutDownloads.remove;"></menuitem>
|
|
||||||
<menuitem id="contextmenu-pause" label="&aboutDownloads.pause;"></menuitem>
|
|
||||||
<menuitem id="contextmenu-resume" label="&aboutDownloads.resume;"></menuitem>
|
|
||||||
<menuitem id="contextmenu-cancel" label="&aboutDownloads.cancel;"></menuitem>
|
|
||||||
<menuitem id="contextmenu-removeall" label="&aboutDownloads.removeAll;"></menuitem>
|
|
||||||
</menu>
|
|
||||||
|
|
||||||
<!--template id="download-item"-->
|
|
||||||
<li id="download-item" class="list-item" role="button" contextmenu="downloadmenu" style="display: none">
|
|
||||||
<img class="icon" src=""/>
|
|
||||||
<div class="details">
|
|
||||||
<div class="row">
|
|
||||||
<!-- This is a hack so that we can crop this label in its center -->
|
|
||||||
<xul:label class="title" crop="center" value=""/>
|
|
||||||
<div class="date"></div>
|
|
||||||
</div>
|
|
||||||
<div class="size"></div>
|
|
||||||
<div class="domain"></div>
|
|
||||||
<div class="state"></div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<!--/template-->
|
|
||||||
|
|
||||||
<div class="header">
|
|
||||||
<div>&aboutDownloads.header;</div>
|
|
||||||
</div>
|
|
||||||
<ul id="private-downloads-list" class="list"></ul>
|
|
||||||
<ul id="public-downloads-list" class="list"></ul>
|
|
||||||
<span id="no-downloads-indicator">&aboutDownloads.empty;</span>
|
|
||||||
<script type="application/javascript" src="chrome://browser/content/aboutDownloads.js"/>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
/* 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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
const { XPCOMUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/XPCOMUtils.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
|
||||||
AndroidLog: "resource://gre/modules/AndroidLog.jsm",
|
|
||||||
EventDispatcher: "resource://gre/modules/Messaging.jsm",
|
|
||||||
});
|
|
||||||
|
|
||||||
const LOGTAG = "Experiments";
|
|
||||||
const EXPERIMENTS_CONFIGURATION =
|
|
||||||
"https://firefox.settings.services.mozilla.com/v1/buckets/fennec/collections/experiments/records";
|
|
||||||
const Experiments = Services.wm.getMostRecentWindow("navigator:browser")
|
|
||||||
.Experiments;
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", initList);
|
|
||||||
|
|
||||||
function log(msg) {
|
|
||||||
AndroidLog.d(LOGTAG, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initList() {
|
|
||||||
const list = document.getElementById("list");
|
|
||||||
list.addEventListener("click", toggleOverride);
|
|
||||||
|
|
||||||
Promise.all([
|
|
||||||
promiseEnabledExperiments(),
|
|
||||||
promiseExperimentsConfiguration(),
|
|
||||||
]).then(values => {
|
|
||||||
const enabledExperiments = values[0];
|
|
||||||
const serverConfiguration = values[1];
|
|
||||||
|
|
||||||
serverConfiguration.data.forEach(function(experiment) {
|
|
||||||
try {
|
|
||||||
let item = document.createElement("li");
|
|
||||||
item.textContent = experiment.name;
|
|
||||||
item.setAttribute("name", experiment.name);
|
|
||||||
item.setAttribute(
|
|
||||||
"isEnabled",
|
|
||||||
enabledExperiments.includes(experiment.name)
|
|
||||||
);
|
|
||||||
list.appendChild(item);
|
|
||||||
} catch (e) {
|
|
||||||
log(`Error while setting experiments list: ${e.error}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleOverride(experiment) {
|
|
||||||
const item = experiment.originalTarget;
|
|
||||||
const name = item.getAttribute("name");
|
|
||||||
const isEnabled = item.getAttribute("isEnabled") === "true";
|
|
||||||
|
|
||||||
log(`toggleOverride: ${name}`);
|
|
||||||
|
|
||||||
Experiments.setOverride(name, !isEnabled);
|
|
||||||
item.setAttribute("isEnabled", !isEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the list of locally enabled experiments.
|
|
||||||
*/
|
|
||||||
function promiseEnabledExperiments() {
|
|
||||||
log("Getting the locally enabled experiments");
|
|
||||||
|
|
||||||
return EventDispatcher.instance
|
|
||||||
.sendRequestForResult({
|
|
||||||
type: "Experiments:GetActive",
|
|
||||||
})
|
|
||||||
.then(experiments => {
|
|
||||||
log("List of locally enabled experiments ready");
|
|
||||||
return experiments;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch the list of experiments from server configuration.
|
|
||||||
*/
|
|
||||||
function promiseExperimentsConfiguration() {
|
|
||||||
log("Fetching server experiments");
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
|
|
||||||
try {
|
|
||||||
xhr.open("GET", EXPERIMENTS_CONFIGURATION, true);
|
|
||||||
} catch (e) {
|
|
||||||
reject(`Error opening request: ${e}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
xhr.onerror = function(e) {
|
|
||||||
reject(`Error making request: ${e.error}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.onload = function(event) {
|
|
||||||
if (xhr.readyState === 4) {
|
|
||||||
if (xhr.status === 200) {
|
|
||||||
try {
|
|
||||||
resolve(JSON.parse(xhr.responseText));
|
|
||||||
} catch (e) {
|
|
||||||
const errorMessage = `Error while parsing request: ${e}`;
|
|
||||||
log(errorMessage);
|
|
||||||
reject(errorMessage);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const errorMessage = `Request to ${url} returned status ${
|
|
||||||
xhr.status
|
|
||||||
}`;
|
|
||||||
log(errorMessage);
|
|
||||||
reject(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log("Finished fetching server experiments");
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.send(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- 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/. -->
|
|
||||||
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<title>Switchboard Experiments</title>
|
|
||||||
<meta name="viewport" content="width=device-width; user-scalable=0" />
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/>
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/aboutExperiments.css" type="text/css"/>
|
|
||||||
<script type="application/javascript" src="chrome://browser/content/aboutExperiments.js"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<ul id="list"/>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<!DOCTYPE html [
|
|
||||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
|
||||||
%htmlDTD;
|
|
||||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
|
||||||
%brandDTD;
|
|
||||||
<!ENTITY % abouthomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
|
|
||||||
%abouthomeDTD;
|
|
||||||
]>
|
|
||||||
|
|
||||||
<!-- 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/. -->
|
|
||||||
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<title>&abouthome.title;</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,628 +0,0 @@
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
ChromeUtils.import(
|
|
||||||
"resource://services-common/utils.js"
|
|
||||||
); /* global: CommonUtils */
|
|
||||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
const { XPCOMUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/XPCOMUtils.jsm"
|
|
||||||
);
|
|
||||||
const { Accounts } = ChromeUtils.import("resource://gre/modules/Accounts.jsm");
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(
|
|
||||||
window,
|
|
||||||
"gChromeWin",
|
|
||||||
() => window.docShell.rootTreeItem.domWindow
|
|
||||||
);
|
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"EventDispatcher",
|
|
||||||
"resource://gre/modules/Messaging.jsm"
|
|
||||||
);
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"Snackbars",
|
|
||||||
"resource://gre/modules/Snackbars.jsm"
|
|
||||||
);
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"Prompt",
|
|
||||||
"resource://gre/modules/Prompt.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
var debug = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/AndroidLog.jsm",
|
|
||||||
{}
|
|
||||||
).AndroidLog.d.bind(null, "AboutLogins");
|
|
||||||
|
|
||||||
var gStringBundle = Services.strings.createBundle(
|
|
||||||
"chrome://browser/locale/aboutLogins.properties"
|
|
||||||
);
|
|
||||||
|
|
||||||
function copyStringShowSnackbar(string, notifyString) {
|
|
||||||
try {
|
|
||||||
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
|
|
||||||
Ci.nsIClipboardHelper
|
|
||||||
);
|
|
||||||
clipboard.copyString(string);
|
|
||||||
Snackbars.show(notifyString, Snackbars.LENGTH_LONG);
|
|
||||||
} catch (e) {
|
|
||||||
debug("Error copying from about:logins");
|
|
||||||
Snackbars.show(
|
|
||||||
gStringBundle.GetStringFromName("loginsDetails.copyFailed"),
|
|
||||||
Snackbars.LENGTH_LONG
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delay filtering while typing in MS
|
|
||||||
const FILTER_DELAY = 500;
|
|
||||||
|
|
||||||
var Logins = {
|
|
||||||
_logins: [],
|
|
||||||
_filterTimer: null,
|
|
||||||
_selectedLogin: null,
|
|
||||||
|
|
||||||
// Load the logins list, displaying interstitial UI (see
|
|
||||||
// #logins-list-loading-body) while loading. There are careful
|
|
||||||
// jank-avoiding measures taken in this function; be careful when
|
|
||||||
// modifying it!
|
|
||||||
//
|
|
||||||
// Returns a Promise that resolves to the list of logins, ordered by
|
|
||||||
// origin.
|
|
||||||
_promiseLogins: function() {
|
|
||||||
let contentBody = document.getElementById("content-body");
|
|
||||||
let emptyBody = document.getElementById("empty-body");
|
|
||||||
let filterIcon = document.getElementById("filter-button");
|
|
||||||
|
|
||||||
let showSpinner = () => {
|
|
||||||
this._toggleListBody(true);
|
|
||||||
emptyBody.classList.add("hidden");
|
|
||||||
};
|
|
||||||
|
|
||||||
let getAllLogins = () => {
|
|
||||||
let logins = [];
|
|
||||||
try {
|
|
||||||
logins = Services.logins.getAllLogins();
|
|
||||||
} catch (e) {
|
|
||||||
// It's likely that the Master Password was not entered; give
|
|
||||||
// a hint to the next person.
|
|
||||||
throw new Error(
|
|
||||||
"Possible Master Password permissions error: " + e.toString()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
logins.sort((a, b) => a.origin.localeCompare(b.origin));
|
|
||||||
|
|
||||||
return logins;
|
|
||||||
};
|
|
||||||
|
|
||||||
let hideSpinner = logins => {
|
|
||||||
this._toggleListBody(false);
|
|
||||||
|
|
||||||
if (!logins.length) {
|
|
||||||
contentBody.classList.add("hidden");
|
|
||||||
filterIcon.classList.add("hidden");
|
|
||||||
emptyBody.classList.remove("hidden");
|
|
||||||
} else {
|
|
||||||
contentBody.classList.remove("hidden");
|
|
||||||
emptyBody.classList.add("hidden");
|
|
||||||
}
|
|
||||||
|
|
||||||
return logins;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return a promise that is resolved after a paint.
|
|
||||||
let waitForPaint = () => {
|
|
||||||
// We're changing 'display'. We need to wait for the new value to take
|
|
||||||
// effect; otherwise, we'll block and never paint a change. Since
|
|
||||||
// requestAnimationFrame callback is generally triggered *before* any
|
|
||||||
// style flush and layout, we wait for two animation frames. This
|
|
||||||
// approach was cribbed from
|
|
||||||
// https://dxr.mozilla.org/mozilla-central/rev/5abe3c4deab94270440422c850bbeaf512b1f38d/browser/base/content/browser-fullScreen.js?offset=0#469.
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// getAllLogins janks the main-thread. We need to paint before that jank;
|
|
||||||
// by throwing the janky load onto the next tick, we paint the spinner; the
|
|
||||||
// spinner is CSS animated off-main-thread.
|
|
||||||
return Promise.resolve()
|
|
||||||
.then(showSpinner)
|
|
||||||
.then(waitForPaint)
|
|
||||||
.then(getAllLogins)
|
|
||||||
.then(hideSpinner);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Reload the logins list, displaying interstitial UI while loading.
|
|
||||||
// Update the stored and displayed list upon completion.
|
|
||||||
_reloadList: function() {
|
|
||||||
this._promiseLogins()
|
|
||||||
.then(logins => {
|
|
||||||
this._logins = logins;
|
|
||||||
this._loadList(logins);
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
// There's no way to recover from errors, sadly. Log and make
|
|
||||||
// it obvious that something is up.
|
|
||||||
this._logins = [];
|
|
||||||
debug("Failed to _reloadList!");
|
|
||||||
Cu.reportError(e);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_toggleListBody: function(isLoading) {
|
|
||||||
let contentBody = document.getElementById("content-body");
|
|
||||||
let loadingBody = document.getElementById("logins-list-loading-body");
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
contentBody.classList.add("hidden");
|
|
||||||
loadingBody.classList.remove("hidden");
|
|
||||||
} else {
|
|
||||||
loadingBody.classList.add("hidden");
|
|
||||||
contentBody.classList.remove("hidden");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
init: function() {
|
|
||||||
window.addEventListener("popstate", this);
|
|
||||||
|
|
||||||
Services.obs.addObserver(this, "passwordmgr-storage-changed");
|
|
||||||
document
|
|
||||||
.getElementById("update-btn")
|
|
||||||
.addEventListener("click", this._onSaveEditLogin.bind(this));
|
|
||||||
document
|
|
||||||
.getElementById("password-btn")
|
|
||||||
.addEventListener("click", this._onPasswordBtn.bind(this));
|
|
||||||
|
|
||||||
let filterInput = document.getElementById("filter-input");
|
|
||||||
let filterContainer = document.getElementById("filter-input-container");
|
|
||||||
|
|
||||||
filterInput.addEventListener("input", event => {
|
|
||||||
// Stop any in-progress filter timer
|
|
||||||
if (this._filterTimer) {
|
|
||||||
clearTimeout(this._filterTimer);
|
|
||||||
this._filterTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a new timer
|
|
||||||
this._filterTimer = setTimeout(() => {
|
|
||||||
this._filter(event);
|
|
||||||
}, FILTER_DELAY);
|
|
||||||
});
|
|
||||||
|
|
||||||
filterInput.addEventListener("blur", event => {
|
|
||||||
filterContainer.setAttribute("hidden", true);
|
|
||||||
});
|
|
||||||
|
|
||||||
document
|
|
||||||
.getElementById("filter-button")
|
|
||||||
.addEventListener("click", event => {
|
|
||||||
filterContainer.removeAttribute("hidden");
|
|
||||||
filterInput.focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("filter-clear").addEventListener("click", event => {
|
|
||||||
// Stop any in-progress filter timer
|
|
||||||
if (this._filterTimer) {
|
|
||||||
clearTimeout(this._filterTimer);
|
|
||||||
this._filterTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
filterInput.blur();
|
|
||||||
filterInput.value = "";
|
|
||||||
this._loadList(this._logins);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._showList();
|
|
||||||
|
|
||||||
this._updatePasswordBtn(true);
|
|
||||||
|
|
||||||
this._reloadList();
|
|
||||||
},
|
|
||||||
|
|
||||||
uninit: function() {
|
|
||||||
Services.obs.removeObserver(this, "passwordmgr-storage-changed");
|
|
||||||
window.removeEventListener("popstate", this);
|
|
||||||
},
|
|
||||||
|
|
||||||
_loadList: function(logins) {
|
|
||||||
let list = document.getElementById("logins-list");
|
|
||||||
let newList = list.cloneNode(false);
|
|
||||||
|
|
||||||
logins.forEach(login => {
|
|
||||||
let item = this._createItemForLogin(login);
|
|
||||||
newList.appendChild(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
list.parentNode.replaceChild(newList, list);
|
|
||||||
},
|
|
||||||
|
|
||||||
_showList: function() {
|
|
||||||
let loginsListPage = document.getElementById("logins-list-page");
|
|
||||||
loginsListPage.classList.remove("hidden");
|
|
||||||
|
|
||||||
let editLoginPage = document.getElementById("edit-login-page");
|
|
||||||
editLoginPage.classList.add("hidden");
|
|
||||||
|
|
||||||
// If the Show/Hide password button has been flipped, reset it
|
|
||||||
if (this._isPasswordBtnInHideMode()) {
|
|
||||||
this._updatePasswordBtn(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_onPopState: function(event) {
|
|
||||||
// Called when back/forward is used to change the state of the page
|
|
||||||
if (event.state) {
|
|
||||||
this._showEditLoginDialog(event.state.id);
|
|
||||||
} else {
|
|
||||||
this._selectedLogin = null;
|
|
||||||
this._showList();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_showEditLoginDialog: function(login) {
|
|
||||||
let listPage = document.getElementById("logins-list-page");
|
|
||||||
listPage.classList.add("hidden");
|
|
||||||
|
|
||||||
let editLoginPage = document.getElementById("edit-login-page");
|
|
||||||
editLoginPage.classList.remove("hidden");
|
|
||||||
|
|
||||||
let usernameField = document.getElementById("username");
|
|
||||||
usernameField.value = login.username;
|
|
||||||
let passwordField = document.getElementById("password");
|
|
||||||
passwordField.value = login.password;
|
|
||||||
let domainField = document.getElementById("origin");
|
|
||||||
domainField.value = login.origin;
|
|
||||||
|
|
||||||
let img = document.getElementById("favicon");
|
|
||||||
this._loadFavicon(img, login.origin);
|
|
||||||
|
|
||||||
let headerText = document.getElementById("edit-login-header-text");
|
|
||||||
if (login.origin && login.origin != "") {
|
|
||||||
headerText.textContent = login.origin;
|
|
||||||
} else {
|
|
||||||
headerText.textContent = gStringBundle.GetStringFromName(
|
|
||||||
"editLogin.fallbackTitle"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
passwordField.addEventListener("input", event => {
|
|
||||||
let newPassword = passwordField.value;
|
|
||||||
let updateBtn = document.getElementById("update-btn");
|
|
||||||
|
|
||||||
if (newPassword === "") {
|
|
||||||
updateBtn.disabled = true;
|
|
||||||
updateBtn.classList.add("disabled-btn");
|
|
||||||
} else if (newPassword !== "" && updateBtn.disabled === true) {
|
|
||||||
updateBtn.disabled = false;
|
|
||||||
updateBtn.classList.remove("disabled-btn");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_onSaveEditLogin: function() {
|
|
||||||
let newUsername = document.getElementById("username").value;
|
|
||||||
let newPassword = document.getElementById("password").value;
|
|
||||||
let origUsername = this._selectedLogin.username;
|
|
||||||
let origPassword = this._selectedLogin.password;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (newUsername === origUsername && newPassword === origPassword) {
|
|
||||||
Snackbars.show(
|
|
||||||
gStringBundle.GetStringFromName("editLogin.saved1"),
|
|
||||||
Snackbars.LENGTH_LONG
|
|
||||||
);
|
|
||||||
this._showList();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let logins = Services.logins.findLogins(
|
|
||||||
this._selectedLogin.origin,
|
|
||||||
this._selectedLogin.formActionOrigin,
|
|
||||||
this._selectedLogin.httpRealm
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let i = 0; i < logins.length; i++) {
|
|
||||||
if (logins[i].username == origUsername) {
|
|
||||||
let propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
|
|
||||||
Ci.nsIWritablePropertyBag
|
|
||||||
);
|
|
||||||
if (newUsername !== origUsername) {
|
|
||||||
propBag.setProperty("username", newUsername);
|
|
||||||
}
|
|
||||||
if (newPassword !== origPassword) {
|
|
||||||
propBag.setProperty("password", newPassword);
|
|
||||||
}
|
|
||||||
// Sync relies on timePasswordChanged to decide whether
|
|
||||||
// or not to sync a login, so touch it.
|
|
||||||
propBag.setProperty("timePasswordChanged", Date.now());
|
|
||||||
Services.logins.modifyLogin(logins[i], propBag);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Snackbars.show(
|
|
||||||
gStringBundle.GetStringFromName("editLogin.couldNotSave"),
|
|
||||||
Snackbars.LENGTH_LONG
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Snackbars.show(
|
|
||||||
gStringBundle.GetStringFromName("editLogin.saved1"),
|
|
||||||
Snackbars.LENGTH_LONG
|
|
||||||
);
|
|
||||||
this._showList();
|
|
||||||
},
|
|
||||||
|
|
||||||
_onPasswordBtn: function() {
|
|
||||||
this._updatePasswordBtn(this._isPasswordBtnInHideMode());
|
|
||||||
},
|
|
||||||
|
|
||||||
_updatePasswordBtn: function(aShouldShow) {
|
|
||||||
let passwordField = document.getElementById("password");
|
|
||||||
let button = document.getElementById("password-btn");
|
|
||||||
let show = gStringBundle.GetStringFromName("password-btn.show");
|
|
||||||
let hide = gStringBundle.GetStringFromName("password-btn.hide");
|
|
||||||
if (aShouldShow) {
|
|
||||||
passwordField.type = "password";
|
|
||||||
button.textContent = show;
|
|
||||||
button.classList.remove("password-btn-hide");
|
|
||||||
} else {
|
|
||||||
passwordField.type = "text";
|
|
||||||
button.textContent = hide;
|
|
||||||
button.classList.add("password-btn-hide");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_isPasswordBtnInHideMode: function() {
|
|
||||||
let button = document.getElementById("password-btn");
|
|
||||||
return button.classList.contains("password-btn-hide");
|
|
||||||
},
|
|
||||||
|
|
||||||
_showPassword: function(password) {
|
|
||||||
let passwordPrompt = new Prompt({
|
|
||||||
window: window,
|
|
||||||
message: password,
|
|
||||||
buttons: [
|
|
||||||
gStringBundle.GetStringFromName("loginsDialog.copy"),
|
|
||||||
gStringBundle.GetStringFromName("loginsDialog.cancel"),
|
|
||||||
],
|
|
||||||
}).show(data => {
|
|
||||||
switch (data.button) {
|
|
||||||
case 0:
|
|
||||||
// Corresponds to "Copy password" button.
|
|
||||||
copyStringShowSnackbar(
|
|
||||||
password,
|
|
||||||
gStringBundle.GetStringFromName("loginsDetails.passwordCopied")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_onLoginClick: function(event) {
|
|
||||||
let loginItem = event.currentTarget;
|
|
||||||
let login = loginItem.login;
|
|
||||||
if (!login) {
|
|
||||||
debug("No login!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let prompt = new Prompt({
|
|
||||||
window: window,
|
|
||||||
});
|
|
||||||
let menuItems = [
|
|
||||||
{ label: gStringBundle.GetStringFromName("loginsMenu.showPassword") },
|
|
||||||
{ label: gStringBundle.GetStringFromName("loginsMenu.copyPassword") },
|
|
||||||
{ label: gStringBundle.GetStringFromName("loginsMenu.copyUsername") },
|
|
||||||
{ label: gStringBundle.GetStringFromName("loginsMenu.editLogin") },
|
|
||||||
{ label: gStringBundle.GetStringFromName("loginsMenu.delete") },
|
|
||||||
{ label: gStringBundle.GetStringFromName("loginsMenu.deleteAll") },
|
|
||||||
];
|
|
||||||
|
|
||||||
prompt.setSingleChoiceItems(menuItems);
|
|
||||||
prompt.show(data => {
|
|
||||||
// Switch on indices of buttons, as they were added when creating login item.
|
|
||||||
switch (data.button) {
|
|
||||||
case 0:
|
|
||||||
this._showPassword(login.password);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
copyStringShowSnackbar(
|
|
||||||
login.password,
|
|
||||||
gStringBundle.GetStringFromName("loginsDetails.passwordCopied")
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
copyStringShowSnackbar(
|
|
||||||
login.username,
|
|
||||||
gStringBundle.GetStringFromName("loginsDetails.usernameCopied")
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
this._selectedLogin = login;
|
|
||||||
this._showEditLoginDialog(login);
|
|
||||||
history.pushState({ id: login.guid }, document.title);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
Accounts.getFirefoxAccount().then(user => {
|
|
||||||
const promptMessage = user
|
|
||||||
? gStringBundle.GetStringFromName(
|
|
||||||
"loginsDialog.confirmDeleteForFxaUser"
|
|
||||||
)
|
|
||||||
: gStringBundle.GetStringFromName("loginsDialog.confirmDelete");
|
|
||||||
const confirmationMessage = gStringBundle.GetStringFromName(
|
|
||||||
"loginsDetails.deleted"
|
|
||||||
);
|
|
||||||
|
|
||||||
this._showConfirmationPrompt(
|
|
||||||
promptMessage,
|
|
||||||
confirmationMessage,
|
|
||||||
() => Services.logins.removeLogin(login)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
Accounts.getFirefoxAccount().then(user => {
|
|
||||||
const promptMessage = user
|
|
||||||
? gStringBundle.GetStringFromName(
|
|
||||||
"loginsDialog.confirmDeleteAllForFxaUser"
|
|
||||||
)
|
|
||||||
: gStringBundle.GetStringFromName(
|
|
||||||
"loginsDialog.confirmDeleteAll"
|
|
||||||
);
|
|
||||||
const confirmationMessage = gStringBundle.GetStringFromName(
|
|
||||||
"loginsDetails.deletedAll"
|
|
||||||
);
|
|
||||||
|
|
||||||
this._showConfirmationPrompt(
|
|
||||||
promptMessage,
|
|
||||||
confirmationMessage,
|
|
||||||
() => Services.logins.removeAllLogins()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_showConfirmationPrompt: function(
|
|
||||||
promptMessage,
|
|
||||||
confirmationMessage,
|
|
||||||
actionToPerform
|
|
||||||
) {
|
|
||||||
new Prompt({
|
|
||||||
window: window,
|
|
||||||
message: promptMessage,
|
|
||||||
buttons: [
|
|
||||||
// Use default, generic values
|
|
||||||
gStringBundle.GetStringFromName("loginsDialog.confirm"),
|
|
||||||
gStringBundle.GetStringFromName("loginsDialog.cancel"),
|
|
||||||
],
|
|
||||||
}).show(data => {
|
|
||||||
switch (data.button) {
|
|
||||||
case 0:
|
|
||||||
// Corresponds to "confirm" button.
|
|
||||||
|
|
||||||
actionToPerform();
|
|
||||||
|
|
||||||
Snackbars.show(confirmationMessage, Snackbars.LENGTH_LONG);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_loadFavicon: function(aImg, aOrigin) {
|
|
||||||
// Load favicon from cache.
|
|
||||||
EventDispatcher.instance
|
|
||||||
.sendRequestForResult({
|
|
||||||
type: "Favicon:Request",
|
|
||||||
url: aOrigin,
|
|
||||||
skipNetwork: true,
|
|
||||||
})
|
|
||||||
.then(
|
|
||||||
function(faviconUrl) {
|
|
||||||
aImg.style.backgroundImage = "url('" + faviconUrl + "')";
|
|
||||||
aImg.style.visibility = "visible";
|
|
||||||
},
|
|
||||||
function(data) {
|
|
||||||
debug("Favicon cache failure : " + data);
|
|
||||||
aImg.style.visibility = "visible";
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
_createItemForLogin: function(login) {
|
|
||||||
let loginItem = document.createElement("div");
|
|
||||||
|
|
||||||
loginItem.setAttribute("loginID", login.guid);
|
|
||||||
loginItem.className = "login-item list-item";
|
|
||||||
|
|
||||||
loginItem.addEventListener("click", this, true);
|
|
||||||
loginItem.addEventListener("contextmenu", this, true);
|
|
||||||
|
|
||||||
// Create item icon.
|
|
||||||
let img = document.createElement("div");
|
|
||||||
img.className = "icon";
|
|
||||||
|
|
||||||
this._loadFavicon(img, login.origin);
|
|
||||||
loginItem.appendChild(img);
|
|
||||||
|
|
||||||
// Create item details.
|
|
||||||
let inner = document.createElement("div");
|
|
||||||
inner.className = "inner";
|
|
||||||
|
|
||||||
let details = document.createElement("div");
|
|
||||||
details.className = "details";
|
|
||||||
inner.appendChild(details);
|
|
||||||
|
|
||||||
let titlePart = document.createElement("div");
|
|
||||||
titlePart.className = "origin";
|
|
||||||
titlePart.textContent = login.origin;
|
|
||||||
details.appendChild(titlePart);
|
|
||||||
|
|
||||||
let versionPart = document.createElement("div");
|
|
||||||
versionPart.textContent = login.httpRealm;
|
|
||||||
versionPart.className = "realm";
|
|
||||||
details.appendChild(versionPart);
|
|
||||||
|
|
||||||
let descPart = document.createElement("div");
|
|
||||||
descPart.textContent = login.username;
|
|
||||||
descPart.className = "username";
|
|
||||||
inner.appendChild(descPart);
|
|
||||||
|
|
||||||
loginItem.appendChild(inner);
|
|
||||||
loginItem.login = login;
|
|
||||||
return loginItem;
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEvent: function(event) {
|
|
||||||
switch (event.type) {
|
|
||||||
case "popstate": {
|
|
||||||
this._onPopState(event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "contextmenu":
|
|
||||||
case "click": {
|
|
||||||
this._onLoginClick(event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
observe: function(subject, topic, data) {
|
|
||||||
switch (topic) {
|
|
||||||
case "passwordmgr-storage-changed": {
|
|
||||||
this._reloadList();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_filter: function(event) {
|
|
||||||
let value = event.target.value.toLowerCase();
|
|
||||||
let logins = this._logins.filter(login => {
|
|
||||||
if (login.origin.toLowerCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (login.username && login.username.toLowerCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (login.httpRealm && login.httpRealm.toLowerCase().includes(value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
this._loadList(logins);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("load", Logins.init.bind(Logins));
|
|
||||||
window.addEventListener("unload", Logins.uninit.bind(Logins));
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
|
||||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
|
|
||||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
|
|
||||||
%globalDTD;
|
|
||||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
|
||||||
%brandDTD;
|
|
||||||
<!ENTITY % aboutDTD SYSTEM "chrome://browser/locale/aboutLogins.dtd" >
|
|
||||||
%aboutDTD;
|
|
||||||
]>
|
|
||||||
<!-- 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/. -->
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<title>&aboutLogins.title;</title>
|
|
||||||
<meta name="viewport" content="width=device-width; user-scalable=0" />
|
|
||||||
<link rel="icon" type="image/png" sizes="64x64" href="chrome://branding/content/favicon64.png" />
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/spinner.css" type="text/css"/>
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/aboutBase.css" type="text/css"/>
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/aboutLogins.css" type="text/css"/>
|
|
||||||
<script type="application/javascript" src="chrome://browser/content/aboutLogins.js"></script>
|
|
||||||
</head>
|
|
||||||
<body dir="&locale.dir;">
|
|
||||||
|
|
||||||
<div id="logins-list-page">
|
|
||||||
<div id="logins-header" class="header">
|
|
||||||
<div>&aboutLogins.title;</div>
|
|
||||||
<ul class="toolbar-buttons">
|
|
||||||
<li id="filter-button"></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div id="content-body">
|
|
||||||
<div id="logins-list" class="list"/>
|
|
||||||
<div id="filter-input-container" hidden="true">
|
|
||||||
<input id="filter-input" type="search"/>
|
|
||||||
<div id="filter-clear"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="logins-list-loading-body" class="hidden">
|
|
||||||
<div id="loading-img-container">
|
|
||||||
|
|
||||||
<div id="spinner" class="mui-refresh-main">
|
|
||||||
<div class="mui-refresh-wrapper">
|
|
||||||
<div class="mui-spinner-wrapper">
|
|
||||||
<div class="mui-spinner-main">
|
|
||||||
<div class="mui-spinner-left">
|
|
||||||
<div class="mui-half-circle-left" />
|
|
||||||
</div>
|
|
||||||
<div class="mui-spinner-right">
|
|
||||||
<div class="mui-half-circle-right" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="empty-body" class="hidden">
|
|
||||||
<div id="empty-obj-text-container">
|
|
||||||
<object type="image/svg+xml" id="empty-icon" data="chrome://browser/skin/images/icon_key_emptypage.svg"/>
|
|
||||||
<div class="empty-text">&aboutLogins.emptyLoginText;</div>
|
|
||||||
<div class="empty-hint">&aboutLogins.emptyLoginHint;</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="edit-login-page" class="hidden">
|
|
||||||
<div id="edit-login-header" class="header">
|
|
||||||
<div id="edit-login-header-text"/>
|
|
||||||
</div>
|
|
||||||
<div class="edit-login-div">
|
|
||||||
<div id="favicon" class="edit-login-icon"/>
|
|
||||||
<input type="text" name="origin" id="origin" class="edit-login-input" disabled="disabled"/>
|
|
||||||
</div>
|
|
||||||
<div class="edit-login-div">
|
|
||||||
<input type="text" name="username" id="username" class="edit-login-input" autocomplete="off"/>
|
|
||||||
</div>
|
|
||||||
<div class="edit-login-div">
|
|
||||||
<input type="password" id="password" name="password" value="password" class="edit-login-input" />
|
|
||||||
<button id="password-btn"></button>
|
|
||||||
</div>
|
|
||||||
<div class="edit-login-div">
|
|
||||||
<button id="update-btn" class="update-button">&aboutLogins.update;</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
/* 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 { XPCOMUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/XPCOMUtils.jsm"
|
|
||||||
);
|
|
||||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
const { PrivateBrowsingUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/PrivateBrowsingUtils.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(
|
|
||||||
window,
|
|
||||||
"gChromeWin",
|
|
||||||
() => window.docShell.rootTreeItem.domWindow
|
|
||||||
);
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
|
||||||
let BrowserApp = window.gChromeWin.BrowserApp;
|
|
||||||
|
|
||||||
if (!PrivateBrowsingUtils.isContentWindowPrivate(window)) {
|
|
||||||
document.body.setAttribute("class", "normal");
|
|
||||||
document
|
|
||||||
.getElementById("newPrivateTabLink")
|
|
||||||
.addEventListener("click", function() {
|
|
||||||
BrowserApp.addTab("about:privatebrowsing", {
|
|
||||||
selected: true,
|
|
||||||
parentId: BrowserApp.selectedTab.id,
|
|
||||||
isPrivate: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
# 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/.
|
|
||||||
-->
|
|
||||||
<!DOCTYPE html [
|
|
||||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
|
||||||
%htmlDTD;
|
|
||||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
|
||||||
%brandDTD;
|
|
||||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
|
||||||
%globalDTD;
|
|
||||||
<!ENTITY % privatebrowsingpageDTD SYSTEM "chrome://browser/locale/aboutPrivateBrowsing.dtd">
|
|
||||||
%privatebrowsingpageDTD;
|
|
||||||
]>
|
|
||||||
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<title>&privatebrowsingpage.title;</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1; user-scalable=no"/>
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/aboutPrivateBrowsing.css" type="text/css" media="all"/>
|
|
||||||
<link rel="icon" type="image/png" href="chrome://branding/content/favicon32.png" />
|
|
||||||
<script type="application/javascript" src="chrome://browser/content/aboutPrivateBrowsing.js"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="private" dir="&locale.dir;">
|
|
||||||
<img class="showPrivate masq" src="chrome://browser/skin/images/privatebrowsing-mask-and-shield.svg" />
|
|
||||||
<img class="showNormal masq" src="chrome://browser/skin/images/privatebrowsing-mask.png" />
|
|
||||||
|
|
||||||
<h1 class="showPrivate">&privatebrowsingpage.title;<br />&privatebrowsingpage.title.private;</h1>
|
|
||||||
<h1 class="showNormal">&privatebrowsingpage.title.normal1;</h1>
|
|
||||||
|
|
||||||
<div class="contentSection">
|
|
||||||
<p class="showPrivate">&privatebrowsingpage.description.trackingProtection;<br /><br />&privatebrowsingpage.description.privateDetails;</p>
|
|
||||||
<p class="showNormal">&privatebrowsingpage.description.normal2;</p>
|
|
||||||
|
|
||||||
<p class="showPrivate"><a href="https://support.mozilla.org/kb/private-browsing-firefox-android">&privatebrowsingpage.link.private;</a></p>
|
|
||||||
<p class="showNormal"><a href="#" id="newPrivateTabLink">&privatebrowsingpage.link.normal;</a></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE html [
|
|
||||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
|
||||||
%htmlDTD;
|
|
||||||
]>
|
|
||||||
|
|
||||||
<!-- 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/. -->
|
|
||||||
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title data-l10n-id="rights-title"></title>
|
|
||||||
<link rel="stylesheet" href="chrome://global/skin/in-content/info-pages.css" type="text/css"/>
|
|
||||||
<link rel="localization" href="branding/brand.ftl"/>
|
|
||||||
<link rel="localization" href="toolkit/about/aboutRights.ftl"/>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body id="your-rights">
|
|
||||||
<div class="container">
|
|
||||||
<h1 data-l10n-id="rights-title"></h1>
|
|
||||||
|
|
||||||
<p data-l10n-id="rights-intro"></p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li data-l10n-id="rights-intro-point-1"><a href="http://www.mozilla.org/MPL/" data-l10n-name="mozilla-public-license-link"></a></li>
|
|
||||||
<!-- Point 2 discusses Mozilla trademarks, and isn't needed when the build is unbranded.
|
|
||||||
- Point 4 discusses privacy policy, unbranded builds get a placeholder (for the vendor to replace)
|
|
||||||
- Point 5 discusses web service terms, unbranded builds gets a placeholder (for the vendor to replace) -->
|
|
||||||
<li data-l10n-id="rights-intro-point-2"><a href="http://www.mozilla.org/foundation/trademarks/policy.html" data-l10n-name="mozilla-trademarks-link"></a></li>
|
|
||||||
<li data-l10n-id="rights-intro-point-3"></li>
|
|
||||||
<li data-l10n-id="rights-intro-point-4"><a href="https://www.mozilla.org/legal/privacy/firefox.html" data-l10n-name="mozilla-privacy-policy-link"></a></li>
|
|
||||||
<li data-l10n-id="rights-intro-point-5"><a href="about:rights#webservices" onclick="showServices();" data-l10n-name="mozilla-service-terms-link"></a></li>
|
|
||||||
<li data-l10n-id="rights-intro-point-6"></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div id="webservices-container">
|
|
||||||
<a name="webservices"/>
|
|
||||||
<h3 data-l10n-id="rights-webservices-header"></h3>
|
|
||||||
|
|
||||||
<p data-l10n-id="rights-webservices"><a href="about:rights#disabling-webservices" onclick="showDisablingServices();" id="showDisablingWebServices" data-l10n-name="mozilla-disable-service-link"></a></p>
|
|
||||||
|
|
||||||
<div id="disabling-webservices-container" style="margin-left:40px;">
|
|
||||||
<a name="disabling-webservices"/>
|
|
||||||
<!-- Safe Browsing cannot be disabled in Firefox Mobile; these instructions show how to do it on desktop
|
|
||||||
<p><strong>&rights.safebrowsing-a;</strong>&rights.safebrowsing-b;</p>
|
|
||||||
<ul>
|
|
||||||
<li>&rights.safebrowsing-term1;</li>
|
|
||||||
<li>&rights.safebrowsing-term2;</li>
|
|
||||||
<li>&rights2.safebrowsing-term3;</li>
|
|
||||||
<li>&rights.safebrowsing-term4;</li>
|
|
||||||
</ul>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<p data-l10n-id="rights-locationawarebrowsing"></p>
|
|
||||||
<ul>
|
|
||||||
<li data-l10n-id="rights-locationawarebrowsing-term-1"></li>
|
|
||||||
<li data-l10n-id="rights-locationawarebrowsing-term-2"></li>
|
|
||||||
<li data-l10n-id="rights-locationawarebrowsing-term-3"></li>
|
|
||||||
<li data-l10n-id="rights-locationawarebrowsing-term-4"></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<!-- Terms only apply to official builds, unbranded builds get a placeholder. -->
|
|
||||||
<li data-l10n-id="rights-webservices-term-1"></li>
|
|
||||||
<li data-l10n-id="rights-webservices-term-2"></li>
|
|
||||||
<li data-l10n-id="rights-webservices-term-3"></li>
|
|
||||||
<li data-l10n-id="rights-webservices-term-4"></li>
|
|
||||||
<li data-l10n-id="rights-webservices-term-5"></li>
|
|
||||||
<li data-l10n-id="rights-webservices-term-6"></li>
|
|
||||||
<li data-l10n-id="rights-webservices-term-7"></li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="application/javascript"><![CDATA[
|
|
||||||
var servicesDiv = document.getElementById("webservices-container");
|
|
||||||
servicesDiv.style.display = "none";
|
|
||||||
|
|
||||||
function showServices() {
|
|
||||||
servicesDiv.style.display = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
var disablingServicesDiv = document.getElementById("disabling-webservices-container");
|
|
||||||
disablingServicesDiv.style.display = "none";
|
|
||||||
|
|
||||||
function showDisablingServices() {
|
|
||||||
disablingServicesDiv.style.display = "";
|
|
||||||
}
|
|
||||||
]]></script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,211 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<!DOCTYPE html [
|
|
||||||
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
|
|
||||||
%htmlDTD;
|
|
||||||
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
|
||||||
%globalDTD;
|
|
||||||
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
|
|
||||||
%brandDTD;
|
|
||||||
<!ENTITY % blockedSiteDTD SYSTEM "chrome://browser/locale/phishing.dtd">
|
|
||||||
%blockedSiteDTD;
|
|
||||||
]>
|
|
||||||
|
|
||||||
<!-- 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/. -->
|
|
||||||
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width; user-scalable=false" />
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/netError.css" media="all" />
|
|
||||||
<link rel="icon" type="image/png" id="favicon" sizes="64x64" href="chrome://browser/skin/images/blocked-warning.png"/>
|
|
||||||
|
|
||||||
<script type="application/javascript"><![CDATA[
|
|
||||||
// Error url MUST be formatted like this:
|
|
||||||
// about:blocked?e=error_code&u=url(&o=1)?
|
|
||||||
// (o=1 when user overrides are allowed)
|
|
||||||
|
|
||||||
// Note that this file uses document.documentURI to get
|
|
||||||
// the URL (with the format from above). This is because
|
|
||||||
// document.location.href gets the current URI off the docshell,
|
|
||||||
// which is the URL displayed in the location bar, i.e.
|
|
||||||
// the URI that the user attempted to load.
|
|
||||||
|
|
||||||
function getErrorCode() {
|
|
||||||
var url = document.documentURI;
|
|
||||||
var error = url.search(/e\=/);
|
|
||||||
var duffUrl = url.search(/\&u\=/);
|
|
||||||
return decodeURIComponent(url.slice(error + 2, duffUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getURL() {
|
|
||||||
var url = document.documentURI;
|
|
||||||
var match = url.match(/&u=([^&]+)&/);
|
|
||||||
|
|
||||||
// match == null if not found; if so, return an empty string
|
|
||||||
// instead of what would turn out to be portions of the URI
|
|
||||||
if (!match)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
url = decodeURIComponent(match[1]);
|
|
||||||
|
|
||||||
// If this is a view-source page, then get then real URI of the page
|
|
||||||
if (/^view-source\:/.test(url))
|
|
||||||
url = url.slice(12);
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether this warning page should be overridable or whether
|
|
||||||
* the "ignore warning" button should be hidden.
|
|
||||||
*/
|
|
||||||
function getOverride() {
|
|
||||||
var url = document.documentURI;
|
|
||||||
var match = url.match(/&o=1&/);
|
|
||||||
return !!match;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to get the hostname via document.location. Fail back
|
|
||||||
* to getURL so that we always return something meaningful.
|
|
||||||
*/
|
|
||||||
function getHostString() {
|
|
||||||
try {
|
|
||||||
return document.location.hostname;
|
|
||||||
} catch (e) {
|
|
||||||
return getURL();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initPage() {
|
|
||||||
var error = "";
|
|
||||||
switch (getErrorCode()) {
|
|
||||||
case "malwareBlocked" :
|
|
||||||
error = "malware";
|
|
||||||
break;
|
|
||||||
case "deceptiveBlocked" :
|
|
||||||
error = "phishing";
|
|
||||||
break;
|
|
||||||
case "unwantedBlocked" :
|
|
||||||
error = "unwanted";
|
|
||||||
break;
|
|
||||||
case "harmfulBlocked" :
|
|
||||||
error = "harmful";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var el;
|
|
||||||
|
|
||||||
if (error !== "malware") {
|
|
||||||
el = document.getElementById("errorTitleText_malware");
|
|
||||||
el.remove();
|
|
||||||
el = document.getElementById("errorShortDescText_malware");
|
|
||||||
el.remove();
|
|
||||||
el = document.getElementById("errorLongDescText_malware");
|
|
||||||
el.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error !== "phishing") {
|
|
||||||
el = document.getElementById("errorTitleText_phishing");
|
|
||||||
el.remove();
|
|
||||||
el = document.getElementById("errorShortDescText_phishing");
|
|
||||||
el.remove();
|
|
||||||
el = document.getElementById("errorLongDescText_phishing");
|
|
||||||
el.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error !== "unwanted") {
|
|
||||||
el = document.getElementById("errorTitleText_unwanted");
|
|
||||||
el.remove();
|
|
||||||
el = document.getElementById("errorShortDescText_unwanted");
|
|
||||||
el.remove();
|
|
||||||
el = document.getElementById("errorLongDescText_unwanted");
|
|
||||||
el.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error !== "harmful") {
|
|
||||||
el = document.getElementById("errorTitleText_harmful");
|
|
||||||
el.remove();
|
|
||||||
el = document.getElementById("errorShortDescText_harmful");
|
|
||||||
el.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!getOverride()) {
|
|
||||||
var btn = document.getElementById("ignoreWarningButton");
|
|
||||||
if (btn) {
|
|
||||||
btn.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sitename
|
|
||||||
let siteElem = document.getElementById(error + "_sitename");
|
|
||||||
if (siteElem) {
|
|
||||||
siteElem.textContent = getHostString();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.title = document.getElementById("errorTitleText_" + error)
|
|
||||||
.innerHTML;
|
|
||||||
|
|
||||||
// Inform the test harness that we're done loading the page
|
|
||||||
var event = new CustomEvent("AboutBlockedLoaded", { bubbles: true });
|
|
||||||
document.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
]]></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body id="errorPage" class="blockedsite" dir="&locale.dir;">
|
|
||||||
|
|
||||||
<div id="errorPageContainer">
|
|
||||||
|
|
||||||
<!-- Error Title -->
|
|
||||||
<div id="errorTitle">
|
|
||||||
<h1 id="errorTitleText_phishing" class="errorTitleText">&safeb.blocked.phishingPage.title3;</h1>
|
|
||||||
<h1 id="errorTitleText_malware" class="errorTitleText">&safeb.blocked.malwarePage.title;</h1>
|
|
||||||
<h1 id="errorTitleText_unwanted" class="errorTitleText">&safeb.blocked.unwantedPage.title;</h1>
|
|
||||||
<h1 id="errorTitleText_harmful" class="errorTitleText">&safeb.blocked.harmfulPage.title;</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="errorLongContent">
|
|
||||||
|
|
||||||
<!-- Short Description -->
|
|
||||||
<div id="errorShortDesc">
|
|
||||||
<p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc3;</p>
|
|
||||||
<p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
|
|
||||||
<p id="errorShortDescText_unwanted">&safeb.blocked.unwantedPage.shortDesc;</p>
|
|
||||||
<p id="errorShortDescText_harmful">&safeb.blocked.harmfulPage.shortDesc;</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Long Description -->
|
|
||||||
<div id="errorLongDesc">
|
|
||||||
<p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc3;</p>
|
|
||||||
<p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
|
|
||||||
<p id="errorLongDescText_unwanted">&safeb.blocked.unwantedPage.longDesc;</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Advisory -->
|
|
||||||
<div id="advisoryDesc">
|
|
||||||
<p id="advisoryDescText">&safeb.palm.advisory.desc;</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action buttons -->
|
|
||||||
<div id="buttons">
|
|
||||||
<!-- Commands handled in browser.js -->
|
|
||||||
<button id="getMeOutButton">&safeb.palm.accept.label;</button>
|
|
||||||
<button id="reportButton">&safeb.palm.reportPage.label;</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="ignoreWarning">
|
|
||||||
<button id="ignoreWarningButton">&safeb.palm.decline.label;</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!--
|
|
||||||
- Note: It is important to run the script this way, instead of using
|
|
||||||
- an onload handler. This is because error pages are loaded as
|
|
||||||
- LOAD_BACKGROUND, which means that onload handlers will not be executed.
|
|
||||||
-->
|
|
||||||
<script type="application/javascript">initPage();</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,17 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<!-- 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/. -->
|
|
||||||
|
|
||||||
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
|
|
||||||
|
|
||||||
<window id="main-window"
|
|
||||||
onload="BrowserApp.startup();"
|
|
||||||
windowtype="navigator:browser"
|
|
||||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
|
||||||
|
|
||||||
<script type="application/javascript" src="chrome://browser/content/browser.js"/>
|
|
||||||
|
|
||||||
<deck id="browsers" flex="1"/>
|
|
||||||
|
|
||||||
</window>
|
|
||||||
|
|
@ -1,637 +0,0 @@
|
||||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
var { XPCOMUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/XPCOMUtils.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"AboutReader",
|
|
||||||
"resource://gre/modules/AboutReader.jsm"
|
|
||||||
);
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"ReaderMode",
|
|
||||||
"resource://gre/modules/ReaderMode.jsm"
|
|
||||||
);
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"Readerable",
|
|
||||||
"resource://gre/modules/Readerable.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "gPipNSSBundle", function() {
|
|
||||||
return Services.strings.createBundle(
|
|
||||||
"chrome://pipnss/locale/pipnss.properties"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "gNSSErrorsBundle", function() {
|
|
||||||
return Services.strings.createBundle(
|
|
||||||
"chrome://pipnss/locale/nsserrors.properties"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
var dump = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/AndroidLog.jsm",
|
|
||||||
{}
|
|
||||||
).AndroidLog.d.bind(null, "Content");
|
|
||||||
|
|
||||||
var global = this;
|
|
||||||
|
|
||||||
var AboutBlockedSiteListener = {
|
|
||||||
init(chromeGlobal) {
|
|
||||||
addEventListener("AboutBlockedLoaded", this, false, true);
|
|
||||||
},
|
|
||||||
|
|
||||||
get isBlockedSite() {
|
|
||||||
return content.document.documentURI.startsWith("about:blocked");
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEvent(aEvent) {
|
|
||||||
if (!this.isBlockedSite) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aEvent.type != "AboutBlockedLoaded") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let provider = "";
|
|
||||||
if (docShell.failedChannel) {
|
|
||||||
let classifiedChannel = docShell.failedChannel.QueryInterface(
|
|
||||||
Ci.nsIClassifiedChannel
|
|
||||||
);
|
|
||||||
if (classifiedChannel) {
|
|
||||||
provider = classifiedChannel.matchedProvider;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let advisoryUrl = Services.prefs.getCharPref(
|
|
||||||
"browser.safebrowsing.provider." + provider + ".advisoryURL",
|
|
||||||
""
|
|
||||||
);
|
|
||||||
if (!advisoryUrl) {
|
|
||||||
let el = content.document.getElementById("advisoryDesc");
|
|
||||||
el.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let advisoryLinkText = Services.prefs.getCharPref(
|
|
||||||
"browser.safebrowsing.provider." + provider + ".advisoryName",
|
|
||||||
""
|
|
||||||
);
|
|
||||||
if (!advisoryLinkText) {
|
|
||||||
let el = content.document.getElementById("advisoryDesc");
|
|
||||||
el.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let anchorEl = content.document.getElementById("advisory_provider");
|
|
||||||
anchorEl.setAttribute("href", advisoryUrl);
|
|
||||||
anchorEl.textContent = advisoryLinkText;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
AboutBlockedSiteListener.init();
|
|
||||||
|
|
||||||
/* The following code, in particular AboutCertErrorListener and
|
|
||||||
* AboutNetErrorListener, is mostly copied from content browser.js and content.js.
|
|
||||||
* Certificate error handling should be unified to remove this duplicated code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
|
|
||||||
const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE;
|
|
||||||
|
|
||||||
const SEC_ERROR_EXPIRED_CERTIFICATE = SEC_ERROR_BASE + 11;
|
|
||||||
const SEC_ERROR_UNKNOWN_ISSUER = SEC_ERROR_BASE + 13;
|
|
||||||
const SEC_ERROR_UNTRUSTED_ISSUER = SEC_ERROR_BASE + 20;
|
|
||||||
const SEC_ERROR_UNTRUSTED_CERT = SEC_ERROR_BASE + 21;
|
|
||||||
const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = SEC_ERROR_BASE + 30;
|
|
||||||
const SEC_ERROR_CA_CERT_INVALID = SEC_ERROR_BASE + 36;
|
|
||||||
const SEC_ERROR_OCSP_FUTURE_RESPONSE = SEC_ERROR_BASE + 131;
|
|
||||||
const SEC_ERROR_OCSP_OLD_RESPONSE = SEC_ERROR_BASE + 132;
|
|
||||||
const SEC_ERROR_REUSED_ISSUER_AND_SERIAL = SEC_ERROR_BASE + 138;
|
|
||||||
const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED = SEC_ERROR_BASE + 176;
|
|
||||||
const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE =
|
|
||||||
MOZILLA_PKIX_ERROR_BASE + 5;
|
|
||||||
const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE =
|
|
||||||
MOZILLA_PKIX_ERROR_BASE + 6;
|
|
||||||
const MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED =
|
|
||||||
MOZILLA_PKIX_ERROR_BASE + 13;
|
|
||||||
|
|
||||||
const SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
|
|
||||||
const SSL_ERROR_SSL_DISABLED = SSL_ERROR_BASE + 20;
|
|
||||||
const SSL_ERROR_SSL2_DISABLED = SSL_ERROR_BASE + 14;
|
|
||||||
|
|
||||||
var AboutNetErrorListener = {
|
|
||||||
init(chromeGlobal) {
|
|
||||||
addEventListener("AboutNetErrorLoad", this, false, true);
|
|
||||||
},
|
|
||||||
|
|
||||||
get isNetErrorSite() {
|
|
||||||
return content.document.documentURI.startsWith("about:neterror");
|
|
||||||
},
|
|
||||||
|
|
||||||
_getErrorMessageFromCode(securityInfo, doc) {
|
|
||||||
let uri = Services.io.newURI(doc.location);
|
|
||||||
let hostString = uri.host;
|
|
||||||
if (uri.port != 443 && uri.port != -1) {
|
|
||||||
hostString += ":" + uri.port;
|
|
||||||
}
|
|
||||||
|
|
||||||
let id_str = "";
|
|
||||||
switch (securityInfo.errorCode) {
|
|
||||||
case SSL_ERROR_SSL_DISABLED:
|
|
||||||
id_str = "PSMERR_SSL_Disabled";
|
|
||||||
break;
|
|
||||||
case SSL_ERROR_SSL2_DISABLED:
|
|
||||||
id_str = "PSMERR_SSL2_Disabled";
|
|
||||||
break;
|
|
||||||
case SEC_ERROR_REUSED_ISSUER_AND_SERIAL:
|
|
||||||
id_str = "PSMERR_HostReusedIssuerSerial";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let nss_error_id_str = securityInfo.errorCodeString;
|
|
||||||
let msg2 = "";
|
|
||||||
if (id_str) {
|
|
||||||
msg2 = gPipNSSBundle.GetStringFromName(id_str) + "\n";
|
|
||||||
} else if (nss_error_id_str) {
|
|
||||||
msg2 = gNSSErrorsBundle.GetStringFromName(nss_error_id_str) + "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!msg2) {
|
|
||||||
// We couldn't get an error message. Use the error string.
|
|
||||||
// Note that this is different from before where we used PR_ErrorToString.
|
|
||||||
msg2 = nss_error_id_str;
|
|
||||||
}
|
|
||||||
let msg = gPipNSSBundle.formatStringFromName("SSLConnectionErrorPrefix2", [
|
|
||||||
hostString,
|
|
||||||
msg2,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (nss_error_id_str) {
|
|
||||||
msg += gPipNSSBundle.formatStringFromName("certErrorCodePrefix3", [
|
|
||||||
nss_error_id_str,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEvent(aEvent) {
|
|
||||||
if (!this.isNetErrorSite) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aEvent.type != "AboutNetErrorLoad") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { securityInfo } = docShell.failedChannel;
|
|
||||||
// We don't have a securityInfo when this is for example a DNS error.
|
|
||||||
if (securityInfo) {
|
|
||||||
securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
|
|
||||||
let msg = this._getErrorMessageFromCode(
|
|
||||||
securityInfo,
|
|
||||||
aEvent.originalTarget.ownerGlobal
|
|
||||||
);
|
|
||||||
let id = content.document.getElementById("errorShortDescText");
|
|
||||||
id.textContent = msg;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
AboutNetErrorListener.init();
|
|
||||||
|
|
||||||
var AboutCertErrorListener = {
|
|
||||||
init(chromeGlobal) {
|
|
||||||
addEventListener("AboutCertErrorLoad", this, false, true);
|
|
||||||
},
|
|
||||||
|
|
||||||
get isCertErrorSite() {
|
|
||||||
return content.document.documentURI.startsWith("about:certerror");
|
|
||||||
},
|
|
||||||
|
|
||||||
_setTechDetailsMsgPart1(hostString, securityInfo, technicalInfo, doc) {
|
|
||||||
let msg = gPipNSSBundle.formatStringFromName("certErrorIntro", [
|
|
||||||
hostString,
|
|
||||||
]);
|
|
||||||
msg += "\n\n";
|
|
||||||
|
|
||||||
if (securityInfo.isUntrusted && !securityInfo.serverCert.isSelfSigned) {
|
|
||||||
switch (securityInfo.errorCode) {
|
|
||||||
case SEC_ERROR_UNKNOWN_ISSUER:
|
|
||||||
msg +=
|
|
||||||
gPipNSSBundle.GetStringFromName("certErrorTrust_UnknownIssuer") +
|
|
||||||
"\n";
|
|
||||||
msg +=
|
|
||||||
gPipNSSBundle.GetStringFromName("certErrorTrust_UnknownIssuer2") +
|
|
||||||
"\n";
|
|
||||||
msg +=
|
|
||||||
gPipNSSBundle.GetStringFromName("certErrorTrust_UnknownIssuer3") +
|
|
||||||
"\n";
|
|
||||||
break;
|
|
||||||
case SEC_ERROR_CA_CERT_INVALID:
|
|
||||||
msg +=
|
|
||||||
gPipNSSBundle.GetStringFromName("certErrorTrust_CaInvalid") + "\n";
|
|
||||||
break;
|
|
||||||
case SEC_ERROR_UNTRUSTED_ISSUER:
|
|
||||||
msg +=
|
|
||||||
gPipNSSBundle.GetStringFromName("certErrorTrust_Issuer") + "\n";
|
|
||||||
break;
|
|
||||||
case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
|
|
||||||
msg +=
|
|
||||||
gPipNSSBundle.GetStringFromName(
|
|
||||||
"certErrorTrust_SignatureAlgorithmDisabled"
|
|
||||||
) + "\n";
|
|
||||||
break;
|
|
||||||
case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
|
|
||||||
msg +=
|
|
||||||
gPipNSSBundle.GetStringFromName("certErrorTrust_ExpiredIssuer") +
|
|
||||||
"\n";
|
|
||||||
break;
|
|
||||||
// This error code currently only exists for the Symantec distrust, we may need to adjust
|
|
||||||
// it to fit other distrusts later.
|
|
||||||
case MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED:
|
|
||||||
msg +=
|
|
||||||
gPipNSSBundle.formatStringFromName("certErrorTrust_Symantec", [
|
|
||||||
hostString,
|
|
||||||
]) + "\n";
|
|
||||||
break;
|
|
||||||
case SEC_ERROR_UNTRUSTED_CERT:
|
|
||||||
default:
|
|
||||||
msg +=
|
|
||||||
gPipNSSBundle.GetStringFromName("certErrorTrust_Untrusted") + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (securityInfo.isUntrusted && securityInfo.serverCert.isSelfSigned) {
|
|
||||||
msg +=
|
|
||||||
gPipNSSBundle.GetStringFromName("certErrorTrust_SelfSigned") + "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
technicalInfo.appendChild(doc.createTextNode(msg));
|
|
||||||
},
|
|
||||||
|
|
||||||
_setTechDetails(securityInfo, location) {
|
|
||||||
if (!securityInfo || !location) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let validity = securityInfo.serverCert.validity;
|
|
||||||
|
|
||||||
let doc = content.document;
|
|
||||||
// CSS class and error code are set from nsDocShell.
|
|
||||||
let searchParams = new URLSearchParams(doc.documentURI.split("?")[1]);
|
|
||||||
let cssClass = searchParams.get("s");
|
|
||||||
let error = searchParams.get("e");
|
|
||||||
let technicalInfo = doc.getElementById("technicalContentText");
|
|
||||||
|
|
||||||
let uri = Services.io.newURI(location);
|
|
||||||
let hostString = uri.host;
|
|
||||||
if (uri.port != 443 && uri.port != -1) {
|
|
||||||
hostString += ":" + uri.port;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This error code currently only exists for the Symantec distrust
|
|
||||||
// in Firefox 63, so we add copy explaining that to the user.
|
|
||||||
// In case of future distrusts of that scale we might need to add
|
|
||||||
// additional parameters that allow us to identify the affected party
|
|
||||||
// without replicating the complex logic from certverifier code.
|
|
||||||
if (
|
|
||||||
securityInfo.errorCode ==
|
|
||||||
MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED
|
|
||||||
) {
|
|
||||||
let introContent = doc.getElementById("introContent");
|
|
||||||
let description = doc.createElement("p");
|
|
||||||
description.textContent = gPipNSSBundle.formatStringFromName(
|
|
||||||
"certErrorSymantecDistrustDescription",
|
|
||||||
[hostString]
|
|
||||||
);
|
|
||||||
introContent.append(description);
|
|
||||||
|
|
||||||
// The regular "what should I do" message does not make sense in this case.
|
|
||||||
doc.getElementById(
|
|
||||||
"whatShouldIDoContentText"
|
|
||||||
).textContent = gPipNSSBundle.GetStringFromName(
|
|
||||||
"certErrorSymantecDistrustAdministrator"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._setTechDetailsMsgPart1(hostString, securityInfo, technicalInfo, doc);
|
|
||||||
|
|
||||||
if (securityInfo.isDomainMismatch) {
|
|
||||||
let subjectAltNamesList = securityInfo.serverCert.subjectAltNames;
|
|
||||||
let subjectAltNames = subjectAltNamesList.split(",");
|
|
||||||
let numSubjectAltNames = subjectAltNames.length;
|
|
||||||
let msgPrefix = "";
|
|
||||||
if (numSubjectAltNames != 0) {
|
|
||||||
if (numSubjectAltNames == 1) {
|
|
||||||
msgPrefix = gPipNSSBundle.GetStringFromName(
|
|
||||||
"certErrorMismatchSinglePrefix"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Let's check if we want to make this a link.
|
|
||||||
let okHost = subjectAltNamesList;
|
|
||||||
let href = "";
|
|
||||||
let thisHost = doc.location.hostname;
|
|
||||||
let proto = doc.location.protocol + "//";
|
|
||||||
// If okHost is a wildcard domain ("*.example.com") let's
|
|
||||||
// use "www" instead. "*.example.com" isn't going to
|
|
||||||
// get anyone anywhere useful. bug 432491
|
|
||||||
okHost = okHost.replace(/^\*\./, "www.");
|
|
||||||
/* case #1:
|
|
||||||
* example.com uses an invalid security certificate.
|
|
||||||
*
|
|
||||||
* The certificate is only valid for www.example.com
|
|
||||||
*
|
|
||||||
* Make sure to include the "." ahead of thisHost so that
|
|
||||||
* a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
|
|
||||||
*
|
|
||||||
* We'd normally just use a RegExp here except that we lack a
|
|
||||||
* library function to escape them properly (bug 248062), and
|
|
||||||
* domain names are famous for having '.' characters in them,
|
|
||||||
* which would allow spurious and possibly hostile matches.
|
|
||||||
*/
|
|
||||||
if (okHost.endsWith("." + thisHost)) {
|
|
||||||
href = proto + okHost;
|
|
||||||
}
|
|
||||||
/* case #2:
|
|
||||||
* browser.garage.maemo.org uses an invalid security certificate.
|
|
||||||
*
|
|
||||||
* The certificate is only valid for garage.maemo.org
|
|
||||||
*/
|
|
||||||
if (thisHost.endsWith("." + okHost)) {
|
|
||||||
href = proto + okHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we set a link, meaning there's something helpful for
|
|
||||||
// the user here, expand the section by default
|
|
||||||
if (href && cssClass != "expertBadCert") {
|
|
||||||
doc.getElementById("technicalContentText").style.display = "block";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the link if we want it.
|
|
||||||
if (href) {
|
|
||||||
let referrerlink = doc.createElement("a");
|
|
||||||
referrerlink.append(subjectAltNamesList + "\n");
|
|
||||||
referrerlink.title = subjectAltNamesList;
|
|
||||||
referrerlink.id = "cert_domain_link";
|
|
||||||
referrerlink.href = href;
|
|
||||||
let fragment = BrowserUtils.getLocalizedFragment(
|
|
||||||
doc,
|
|
||||||
msgPrefix,
|
|
||||||
referrerlink
|
|
||||||
);
|
|
||||||
technicalInfo.appendChild(fragment);
|
|
||||||
} else {
|
|
||||||
let fragment = BrowserUtils.getLocalizedFragment(
|
|
||||||
doc,
|
|
||||||
msgPrefix,
|
|
||||||
subjectAltNamesList
|
|
||||||
);
|
|
||||||
technicalInfo.appendChild(fragment);
|
|
||||||
}
|
|
||||||
technicalInfo.append("\n");
|
|
||||||
} else {
|
|
||||||
let msg =
|
|
||||||
gPipNSSBundle.GetStringFromName("certErrorMismatchMultiple") + "\n";
|
|
||||||
for (let i = 0; i < numSubjectAltNames; i++) {
|
|
||||||
msg += subjectAltNames[i];
|
|
||||||
if (i != numSubjectAltNames - 1) {
|
|
||||||
msg += ", ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
technicalInfo.append(msg + "\n");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let msg = gPipNSSBundle.formatStringFromName("certErrorMismatch", [
|
|
||||||
hostString,
|
|
||||||
]);
|
|
||||||
technicalInfo.append(msg + "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (securityInfo.isNotValidAtThisTime) {
|
|
||||||
let nowTime = new Date().getTime() * 1000;
|
|
||||||
let dateOptions = {
|
|
||||||
year: "numeric",
|
|
||||||
month: "long",
|
|
||||||
day: "numeric",
|
|
||||||
hour: "numeric",
|
|
||||||
minute: "numeric",
|
|
||||||
};
|
|
||||||
let now = new Services.intl.DateTimeFormat(undefined, dateOptions).format(
|
|
||||||
new Date()
|
|
||||||
);
|
|
||||||
let msg = "";
|
|
||||||
if (validity.notBefore) {
|
|
||||||
if (nowTime > validity.notAfter) {
|
|
||||||
msg +=
|
|
||||||
gPipNSSBundle.formatStringFromName("certErrorExpiredNow", [
|
|
||||||
validity.notAfterLocalTime,
|
|
||||||
now,
|
|
||||||
]) + "\n";
|
|
||||||
} else {
|
|
||||||
msg +=
|
|
||||||
gPipNSSBundle.formatStringFromName("certErrorNotYetValidNow", [
|
|
||||||
validity.notBeforeLocalTime,
|
|
||||||
now,
|
|
||||||
]) + "\n";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If something goes wrong, we assume the cert expired.
|
|
||||||
msg +=
|
|
||||||
gPipNSSBundle.formatStringFromName("certErrorExpiredNow", ["", now]) +
|
|
||||||
"\n";
|
|
||||||
}
|
|
||||||
technicalInfo.append(msg);
|
|
||||||
}
|
|
||||||
technicalInfo.append("\n");
|
|
||||||
|
|
||||||
// Add link to certificate and error message.
|
|
||||||
let errorCodeMsg = gPipNSSBundle.formatStringFromName(
|
|
||||||
"certErrorCodePrefix3",
|
|
||||||
[securityInfo.errorCodeString]
|
|
||||||
);
|
|
||||||
technicalInfo.append(errorCodeMsg);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEvent(aEvent) {
|
|
||||||
if (!this.isCertErrorSite) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aEvent.type != "AboutCertErrorLoad") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ownerDoc = aEvent.originalTarget.ownerGlobal;
|
|
||||||
let securityInfo =
|
|
||||||
docShell.failedChannel && docShell.failedChannel.securityInfo;
|
|
||||||
securityInfo
|
|
||||||
.QueryInterface(Ci.nsITransportSecurityInfo)
|
|
||||||
.QueryInterface(Ci.nsISerializable);
|
|
||||||
this._setTechDetails(securityInfo, ownerDoc.location.href);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
AboutCertErrorListener.init();
|
|
||||||
|
|
||||||
// This is copied from desktop's tab-content.js. See bug 1153485 about sharing this code somehow.
|
|
||||||
var AboutReaderListener = {
|
|
||||||
_articlePromise: null,
|
|
||||||
|
|
||||||
_isLeavingReaderableReaderMode: false,
|
|
||||||
|
|
||||||
init: function() {
|
|
||||||
addEventListener("AboutReaderContentLoaded", this, false, true);
|
|
||||||
addEventListener("DOMContentLoaded", this, false);
|
|
||||||
addEventListener("pageshow", this, false);
|
|
||||||
addEventListener("pagehide", this, false);
|
|
||||||
addMessageListener("Reader:ToggleReaderMode", this);
|
|
||||||
addMessageListener("Reader:PushState", this);
|
|
||||||
},
|
|
||||||
|
|
||||||
receiveMessage: function(message) {
|
|
||||||
switch (message.name) {
|
|
||||||
case "Reader:ToggleReaderMode":
|
|
||||||
let url = content.document.location.href;
|
|
||||||
if (!this.isAboutReader) {
|
|
||||||
this._articlePromise = ReaderMode.parseDocument(
|
|
||||||
content.document
|
|
||||||
).catch(Cu.reportError);
|
|
||||||
ReaderMode.enterReaderMode(docShell, content);
|
|
||||||
} else {
|
|
||||||
this._isLeavingReaderableReaderMode = this.isReaderableAboutReader;
|
|
||||||
ReaderMode.leaveReaderMode(docShell, content);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "Reader:PushState":
|
|
||||||
this.updateReaderButton(!!(message.data && message.data.isArticle));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
get isAboutReader() {
|
|
||||||
return content.document.documentURI.startsWith("about:reader");
|
|
||||||
},
|
|
||||||
|
|
||||||
get isReaderableAboutReader() {
|
|
||||||
return (
|
|
||||||
this.isAboutReader && !content.document.documentElement.dataset.isError
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
get isErrorPage() {
|
|
||||||
return (
|
|
||||||
content.document.documentURI.startsWith("about:neterror") ||
|
|
||||||
content.document.documentURI.startsWith("about:certerror") ||
|
|
||||||
content.document.documentURI.startsWith("about:blocked")
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleEvent: function(aEvent) {
|
|
||||||
if (aEvent.originalTarget.defaultView != content) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (aEvent.type) {
|
|
||||||
case "AboutReaderContentLoaded":
|
|
||||||
if (!this.isAboutReader) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are restoring multiple reader mode tabs during session restore, duplicate "DOMContentLoaded"
|
|
||||||
// events may be fired for the visible tab. The inital "DOMContentLoaded" may be received before the
|
|
||||||
// document body is available, so we avoid instantiating an AboutReader object, expecting that a
|
|
||||||
// valid message will follow. See bug 925983.
|
|
||||||
if (content.document.body) {
|
|
||||||
new AboutReader(global, content, this._articlePromise);
|
|
||||||
this._articlePromise = null;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "pagehide":
|
|
||||||
// this._isLeavingReaderableReaderMode is used here to keep the Reader Mode icon
|
|
||||||
// visible in the location bar when transitioning from reader-mode page
|
|
||||||
// back to the source page.
|
|
||||||
sendAsyncMessage("Reader:UpdateReaderButton", {
|
|
||||||
isArticle: this._isLeavingReaderableReaderMode,
|
|
||||||
});
|
|
||||||
if (this._isLeavingReaderableReaderMode) {
|
|
||||||
this._isLeavingReaderableReaderMode = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "pageshow":
|
|
||||||
// If a page is loaded from the bfcache, we won't get a "DOMContentLoaded"
|
|
||||||
// event, so we need to rely on "pageshow" in this case.
|
|
||||||
if (aEvent.persisted) {
|
|
||||||
this.updateReaderButton();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "DOMContentLoaded":
|
|
||||||
this.updateReaderButton();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateReaderButton: function(forceNonArticle) {
|
|
||||||
// Do not show Reader View icon on error pages (bug 1320900)
|
|
||||||
if (this.isErrorPage) {
|
|
||||||
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false });
|
|
||||||
} else if (
|
|
||||||
!Readerable.isEnabledForParseOnLoad ||
|
|
||||||
this.isAboutReader ||
|
|
||||||
!(content.document instanceof content.HTMLDocument) ||
|
|
||||||
content.document.mozSyntheticDocument
|
|
||||||
) {
|
|
||||||
} else {
|
|
||||||
this.scheduleReadabilityCheckPostPaint(forceNonArticle);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelPotentialPendingReadabilityCheck: function() {
|
|
||||||
if (this._pendingReadabilityCheck) {
|
|
||||||
removeEventListener("MozAfterPaint", this._pendingReadabilityCheck);
|
|
||||||
delete this._pendingReadabilityCheck;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
scheduleReadabilityCheckPostPaint: function(forceNonArticle) {
|
|
||||||
if (this._pendingReadabilityCheck) {
|
|
||||||
// We need to stop this check before we re-add one because we don't know
|
|
||||||
// if forceNonArticle was true or false last time.
|
|
||||||
this.cancelPotentialPendingReadabilityCheck();
|
|
||||||
}
|
|
||||||
this._pendingReadabilityCheck = this.onPaintWhenWaitedFor.bind(
|
|
||||||
this,
|
|
||||||
forceNonArticle
|
|
||||||
);
|
|
||||||
addEventListener("MozAfterPaint", this._pendingReadabilityCheck);
|
|
||||||
},
|
|
||||||
|
|
||||||
onPaintWhenWaitedFor: function(forceNonArticle, event) {
|
|
||||||
// In non-e10s, we'll get called for paints other than ours, and so it's
|
|
||||||
// possible that this page hasn't been laid out yet, in which case we
|
|
||||||
// should wait until we get an event that does relate to our layout. We
|
|
||||||
// determine whether any of our content got painted by checking if there
|
|
||||||
// are any painted rects.
|
|
||||||
if (!event.clientRects.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Services.console.logStringMessage(`ON PAINT WHEN WAITED FOR\n`);
|
|
||||||
this.cancelPotentialPendingReadabilityCheck();
|
|
||||||
|
|
||||||
// Only send updates when there are articles; there's no point updating with
|
|
||||||
// |false| all the time.
|
|
||||||
if (Readerable.isProbablyReaderable(content.document)) {
|
|
||||||
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true });
|
|
||||||
} else if (forceNonArticle) {
|
|
||||||
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
AboutReaderListener.init();
|
|
||||||
|
|
||||||
Services.obs.notifyObservers(this, "tab-content-frameloader-created");
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
# 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/.
|
|
||||||
|
|
||||||
# LOCALIZATION NOTE: do not localize
|
|
||||||
af=Afrikaans
|
|
||||||
ak=Akan
|
|
||||||
ar=عربي
|
|
||||||
as=অসমীয়া
|
|
||||||
ast-ES=Asturianu
|
|
||||||
be=Беларуская
|
|
||||||
bg=Български
|
|
||||||
bn-BD=বাংলা (বাংলাদেশ)
|
|
||||||
bn-IN=বাংলা (ভারত)
|
|
||||||
br-FR=Brezhoneg
|
|
||||||
ca=català
|
|
||||||
ca-valencia=català (valencià)
|
|
||||||
cs=Čeština
|
|
||||||
cy=Cymraeg
|
|
||||||
da=Dansk
|
|
||||||
de=Deutsch
|
|
||||||
de-AT=Deutsch (Österreich)
|
|
||||||
de-CH=Deutsch (Schweiz)
|
|
||||||
de-DE=Deutsch (Deutschland)
|
|
||||||
el=Ελληνικά
|
|
||||||
en-AU=English (Australian)
|
|
||||||
en-CA=English (Canadian)
|
|
||||||
en-GB=English (British)
|
|
||||||
en-NZ=English (New Zealand)
|
|
||||||
en-US=English (US)
|
|
||||||
en-ZA=English (South African)
|
|
||||||
eo=Esperanto
|
|
||||||
es-AR=Español (de Argentina)
|
|
||||||
es-CL=Español (de Chile)
|
|
||||||
es-ES=Español (de España)
|
|
||||||
es-MX=Español (de México)
|
|
||||||
et=Eesti keel
|
|
||||||
eu=Euskara
|
|
||||||
fa=فارسی
|
|
||||||
fi=suomi
|
|
||||||
fr=Français
|
|
||||||
fur-IT=Furlan
|
|
||||||
fy-NL=Frysk
|
|
||||||
ga-IE=Gaeilge
|
|
||||||
gl=Galego
|
|
||||||
gu-IN=ગુજરાતી
|
|
||||||
he=עברית
|
|
||||||
hi=हिन्दी
|
|
||||||
hi-IN=हिन्दी (भारत)
|
|
||||||
hr=Hrvatski
|
|
||||||
hsb=Hornjoserbsce
|
|
||||||
hu=Magyar
|
|
||||||
hy-AM=Հայերեն
|
|
||||||
id=Bahasa Indonesia
|
|
||||||
is=íslenska
|
|
||||||
it=Italiano
|
|
||||||
ja=日本語
|
|
||||||
ka=ქართული
|
|
||||||
kk=Қазақ
|
|
||||||
kn=ಕನ್ನಡ
|
|
||||||
ko=한국어
|
|
||||||
ku=Kurdî
|
|
||||||
la=Latina
|
|
||||||
lt=lietuvių
|
|
||||||
lv=Latviešu
|
|
||||||
mg=Malagasy
|
|
||||||
mi=Māori (Aotearoa)
|
|
||||||
mk=Македонски
|
|
||||||
ml=മലയാളം
|
|
||||||
mn=Монгол
|
|
||||||
mr=मराठी
|
|
||||||
nb-NO=Norsk bokmål
|
|
||||||
ne-NP=नेपाली
|
|
||||||
nl=Nederlands
|
|
||||||
nn-NO=Norsk nynorsk
|
|
||||||
nr=isiNdebele Sepumalanga
|
|
||||||
nso=Sepedi
|
|
||||||
oc=occitan (lengadocian)
|
|
||||||
or=ଓଡ଼ିଆ
|
|
||||||
pa-IN=ਪੰਜਾਬੀ
|
|
||||||
pl=Polski
|
|
||||||
pt-BR=Português (do Brasil)
|
|
||||||
pt-PT=Português (Europeu)
|
|
||||||
rm=rumantsch
|
|
||||||
ro=română
|
|
||||||
ru=Русский
|
|
||||||
rw=Ikinyarwanda
|
|
||||||
si=සිංහල
|
|
||||||
sk=slovenčina
|
|
||||||
sl=slovensko
|
|
||||||
sq=Shqip
|
|
||||||
sr=Српски
|
|
||||||
sr-Latn=Srpski
|
|
||||||
ss=Siswati
|
|
||||||
st=Sesotho
|
|
||||||
sv-SE=Svenska
|
|
||||||
ta=தமிழ்
|
|
||||||
ta-IN=தமிழ் (இந்தியா)
|
|
||||||
ta-LK=தமிழ் (இலங்கை)
|
|
||||||
te=తెలుగు
|
|
||||||
th=ไทย
|
|
||||||
tn=Setswana
|
|
||||||
tr=Türkçe
|
|
||||||
ts=Mutsonga
|
|
||||||
tt-RU=Tatarça
|
|
||||||
uk=Українська
|
|
||||||
ur=اُردو
|
|
||||||
ve=Tshivenḓa
|
|
||||||
vi=Tiếng Việt
|
|
||||||
wo=Wolof
|
|
||||||
xh=isiXhosa
|
|
||||||
zh-CN=中文 (简体)
|
|
||||||
zh-TW=正體中文 (繁體)
|
|
||||||
zu=isiZulu
|
|
||||||
|
|
@ -1,315 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<!DOCTYPE html [
|
|
||||||
<!ENTITY % htmlDTD
|
|
||||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
|
||||||
"DTD/xhtml1-strict.dtd">
|
|
||||||
%htmlDTD;
|
|
||||||
<!ENTITY % netErrorDTD
|
|
||||||
SYSTEM "chrome://global/locale/netError.dtd">
|
|
||||||
%netErrorDTD;
|
|
||||||
<!ENTITY % globalDTD
|
|
||||||
SYSTEM "chrome://global/locale/global.dtd">
|
|
||||||
%globalDTD;
|
|
||||||
]>
|
|
||||||
|
|
||||||
<!-- 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/. -->
|
|
||||||
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width; user-scalable=false;" />
|
|
||||||
<title>&loadError.label;</title>
|
|
||||||
<link rel="stylesheet" href="chrome://browser/skin/netError.css" media="all" />
|
|
||||||
<!-- If the location of the favicon is changed here, the FAVICON_ERRORPAGE_URL symbol in
|
|
||||||
toolkit/components/places/src/nsFaviconService.h should be updated. -->
|
|
||||||
<link rel="icon" type="image/png" id="favicon" sizes="64x64" href="chrome://browser/skin/images/errorpage-warning.png"/>
|
|
||||||
|
|
||||||
<script type="application/javascript"><![CDATA[
|
|
||||||
// Error url MUST be formatted like this:
|
|
||||||
// moz-neterror:page?e=error&u=url&d=desc
|
|
||||||
//
|
|
||||||
// or optionally, to specify an alternate CSS class to allow for
|
|
||||||
// custom styling and favicon:
|
|
||||||
//
|
|
||||||
// moz-neterror:page?e=error&u=url&s=classname&d=desc
|
|
||||||
|
|
||||||
// Note that this file uses document.documentURI to get
|
|
||||||
// the URL (with the format from above). This is because
|
|
||||||
// document.location.href gets the current URI off the docshell,
|
|
||||||
// which is the URL displayed in the location bar, i.e.
|
|
||||||
// the URI that the user attempted to load.
|
|
||||||
|
|
||||||
function getErrorCode() {
|
|
||||||
var url = document.documentURI;
|
|
||||||
var error = url.search(/e\=/);
|
|
||||||
var duffUrl = url.search(/\&u\=/);
|
|
||||||
return decodeURIComponent(url.slice(error + 2, duffUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCSSClass() {
|
|
||||||
var url = document.documentURI;
|
|
||||||
var matches = url.match(/s\=([^&]+)\&/);
|
|
||||||
// s is optional, if no match just return nothing
|
|
||||||
if (!matches || matches.length < 2)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
// parenthetical match is the second entry
|
|
||||||
return decodeURIComponent(matches[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDescription() {
|
|
||||||
var url = document.documentURI;
|
|
||||||
var desc = url.search(/d\=/);
|
|
||||||
|
|
||||||
// desc == -1 if not found; if so, return an empty string
|
|
||||||
// instead of what would turn out to be portions of the URI
|
|
||||||
if (desc == -1)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
return decodeURIComponent(url.slice(desc + 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
function retryThis(buttonEl) {
|
|
||||||
// Note: The application may wish to handle switching off "offline mode"
|
|
||||||
// before this event handler runs, but using a capturing event handler.
|
|
||||||
|
|
||||||
// Session history has the URL of the page that failed
|
|
||||||
// to load, not the one of the error page. So, just call
|
|
||||||
// reload(), which will also repost POST data correctly.
|
|
||||||
try {
|
|
||||||
location.reload();
|
|
||||||
} catch (e) {
|
|
||||||
// We probably tried to reload a URI that caused an exception to
|
|
||||||
// occur; e.g. a nonexistent file.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initPage() {
|
|
||||||
var err = getErrorCode();
|
|
||||||
|
|
||||||
// if it's an unknown error or there's no title or description
|
|
||||||
// defined, get the generic message
|
|
||||||
var errTitle = document.getElementById("et_" + err);
|
|
||||||
var errDesc = document.getElementById("ed_" + err);
|
|
||||||
if (!errTitle || !errDesc) {
|
|
||||||
errTitle = document.getElementById("et_generic");
|
|
||||||
errDesc = document.getElementById("ed_generic");
|
|
||||||
}
|
|
||||||
|
|
||||||
var title = document.getElementsByClassName("errorTitleText")[0];
|
|
||||||
if (title) {
|
|
||||||
title.parentNode.replaceChild(errTitle, title);
|
|
||||||
// change id to the replaced child's id so styling works
|
|
||||||
errTitle.classList.add("errorTitleText");
|
|
||||||
}
|
|
||||||
|
|
||||||
var ld = document.getElementById("errorLongDesc");
|
|
||||||
if (ld) {
|
|
||||||
ld.parentNode.replaceChild(errDesc, ld);
|
|
||||||
// change id to the replaced child's id so styling works
|
|
||||||
errDesc.id = "errorLongDesc";
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove undisplayed errors to avoid bug 39098
|
|
||||||
var errContainer = document.getElementById("errorContainer");
|
|
||||||
errContainer.remove();
|
|
||||||
|
|
||||||
var className = getCSSClass();
|
|
||||||
if (className && className != "expertBadCert") {
|
|
||||||
// Associate a CSS class with the root of the page, if one was passed in,
|
|
||||||
// to allow custom styling.
|
|
||||||
// Not "expertBadCert" though, don't want to deal with the favicon
|
|
||||||
document.documentElement.className = className;
|
|
||||||
|
|
||||||
// Also, if they specified a CSS class, they must supply their own
|
|
||||||
// favicon. In order to trigger the browser to repaint though, we
|
|
||||||
// need to remove/add the link element.
|
|
||||||
var favicon = document.getElementById("favicon");
|
|
||||||
var faviconParent = favicon.parentNode;
|
|
||||||
faviconParent.removeChild(favicon);
|
|
||||||
favicon.setAttribute("href", "chrome://global/skin/icons/" + className + "_favicon.png");
|
|
||||||
faviconParent.appendChild(favicon);
|
|
||||||
}
|
|
||||||
if (className == "expertBadCert") {
|
|
||||||
showSecuritySection();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err == "remoteXUL") {
|
|
||||||
// Remove the "Try again" button for remote XUL errors given that
|
|
||||||
// it is useless.
|
|
||||||
document.getElementById("errorTryAgain").style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err == "cspBlocked") {
|
|
||||||
// Remove the "Try again" button for CSP violations, since it's
|
|
||||||
// almost certainly useless. (Bug 553180)
|
|
||||||
document.getElementById("errorTryAgain").style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err == "nssBadCert") {
|
|
||||||
// Remove the "Try again" button for security exceptions, since it's
|
|
||||||
// almost certainly useless.
|
|
||||||
document.getElementById("errorTryAgain").style.display = "none";
|
|
||||||
document.getElementById("errorPage").setAttribute("class", "certerror");
|
|
||||||
} else {
|
|
||||||
// Remove the override block for non-certificate errors. CSS-hiding
|
|
||||||
// isn't good enough here, because of bug 39098
|
|
||||||
var secOverride = document.getElementById("securityOverrideDiv");
|
|
||||||
secOverride.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err != "nssBadCert" && err != "nssFailure2") {
|
|
||||||
var sd = document.getElementById("errorShortDescText");
|
|
||||||
if (sd) {
|
|
||||||
sd.textContent = getDescription();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err == "inadequateSecurityError") {
|
|
||||||
// Remove the "Try again" button for HTTP/2 inadequate security as it
|
|
||||||
// is useless.
|
|
||||||
document.getElementById("errorTryAgain").style.display = "none";
|
|
||||||
|
|
||||||
var container = document.getElementById("errorLongDesc");
|
|
||||||
for (var span of container.querySelectorAll("span.hostname")) {
|
|
||||||
span.textContent = document.location.hostname;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var event = new CustomEvent("AboutNetErrorLoad", {bubbles: true});
|
|
||||||
document.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showSecuritySection() {
|
|
||||||
// Swap link out, content in
|
|
||||||
document.getElementById("securityOverrideContent").style.display = "";
|
|
||||||
document.getElementById("securityOverrideLink").style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
function createLink(el, id, text) {
|
|
||||||
var anchorEl = document.createElement("a");
|
|
||||||
anchorEl.setAttribute("id", id);
|
|
||||||
anchorEl.setAttribute("title", text);
|
|
||||||
anchorEl.appendChild(document.createTextNode(text));
|
|
||||||
el.appendChild(anchorEl);
|
|
||||||
}
|
|
||||||
]]></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body id="errorPage" dir="&locale.dir;">
|
|
||||||
|
|
||||||
<!-- ERROR ITEM CONTAINER (removed during loading to avoid bug 39098) -->
|
|
||||||
<div id="errorContainer">
|
|
||||||
<div id="errorTitlesContainer">
|
|
||||||
<h1 id="et_generic">&generic.title;</h1>
|
|
||||||
<h1 id="et_dnsNotFound">&dnsNotFound.title;</h1>
|
|
||||||
<h1 id="et_fileNotFound">&fileNotFound.title;</h1>
|
|
||||||
<h1 id="et_fileAccessDenied">&fileAccessDenied.title;</h1>
|
|
||||||
<h1 id="et_malformedURI">&malformedURI.title;</h1>
|
|
||||||
<h1 id="et_unknownProtocolFound">&unknownProtocolFound.title;</h1>
|
|
||||||
<h1 id="et_connectionFailure">&connectionFailure.title;</h1>
|
|
||||||
<h1 id="et_netTimeout">&netTimeout.title;</h1>
|
|
||||||
<h1 id="et_redirectLoop">&redirectLoop.title;</h1>
|
|
||||||
<h1 id="et_unknownSocketType">&unknownSocketType.title;</h1>
|
|
||||||
<h1 id="et_netReset">&netReset.title;</h1>
|
|
||||||
<h1 id="et_notCached">¬Cached.title;</h1>
|
|
||||||
|
|
||||||
<!-- Since Fennec not yet have offline mode, change the title to
|
|
||||||
connectionFailure to prevent confusion -->
|
|
||||||
<h1 id="et_netOffline">&connectionFailure.title;</h1>
|
|
||||||
|
|
||||||
<h1 id="et_netInterrupt">&netInterrupt.title;</h1>
|
|
||||||
<h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
|
|
||||||
<h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
|
|
||||||
<h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
|
|
||||||
<h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
|
|
||||||
<h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
|
|
||||||
<h1 id="et_nssFailure2">&nssFailure2.title;</h1>
|
|
||||||
<h1 id="et_nssBadCert">&nssBadCert.title;</h1>
|
|
||||||
<h1 id="et_cspBlocked">&cspBlocked.title;</h1>
|
|
||||||
<h1 id="et_remoteXUL">&remoteXUL.title;</h1>
|
|
||||||
<h1 id="et_corruptedContentErrorv2">&corruptedContentErrorv2.title;</h1>
|
|
||||||
<h1 id="et_sslv3Used">&sslv3Used.title;</h1>
|
|
||||||
<h1 id="et_weakCryptoUsed">&weakCryptoUsed.title;</h1>
|
|
||||||
<h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1>
|
|
||||||
<h1 id="et_networkProtocolError">&networkProtocolError.title;</h1>
|
|
||||||
</div>
|
|
||||||
<div id="errorDescriptionsContainer">
|
|
||||||
<div id="ed_generic">&generic.longDesc;</div>
|
|
||||||
<div id="ed_dnsNotFound">&dnsNotFound.longDesc4;</div>
|
|
||||||
<div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
|
|
||||||
<div id="ed_fileAccessDenied">&fileAccessDenied.longDesc;</div>
|
|
||||||
<div id="ed_malformedURI">&malformedURI.longDesc2;</div>
|
|
||||||
<div id="ed_unknownProtocolFound">&unknownProtocolFound.longDesc;</div>
|
|
||||||
<div id="ed_connectionFailure">&connectionFailure.longDesc2;</div>
|
|
||||||
<div id="ed_netTimeout">&netTimeout.longDesc2;</div>
|
|
||||||
<div id="ed_redirectLoop">&redirectLoop.longDesc;</div>
|
|
||||||
<div id="ed_unknownSocketType">&unknownSocketType.longDesc;</div>
|
|
||||||
<div id="ed_netReset">&netReset.longDesc2;</div>
|
|
||||||
<div id="ed_notCached">¬Cached.longDesc;</div>
|
|
||||||
|
|
||||||
<!-- Change longDesc from netOffline to connectionFailure,
|
|
||||||
suggesting user to check their wifi/cell_data connection -->
|
|
||||||
<div id="ed_netOffline">&connectionFailure.longDesc2;</div>
|
|
||||||
|
|
||||||
<div id="ed_netInterrupt">&netInterrupt.longDesc2;</div>
|
|
||||||
<div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
|
|
||||||
<div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc3;</div>
|
|
||||||
<div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
|
|
||||||
<div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
|
|
||||||
<div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
|
|
||||||
<div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
|
|
||||||
<div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
|
|
||||||
<div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
|
|
||||||
<div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
|
|
||||||
<div id="ed_corruptedContentErrorv2">&corruptedContentErrorv2.longDesc;</div>
|
|
||||||
<div id="ed_sslv3Used">&sslv3Used.longDesc;</div>
|
|
||||||
<div id="ed_weakCryptoUsed">&weakCryptoUsed.longDesc;</div>
|
|
||||||
<div id="ed_inadequateSecurityError">&inadequateSecurityError.longDesc;</div>
|
|
||||||
<div id="ed_networkProtocolError">&networkProtocolError.longDesc;</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- PAGE CONTAINER (for styling purposes only) -->
|
|
||||||
<div id="errorPageContainer">
|
|
||||||
|
|
||||||
<!-- Error Title -->
|
|
||||||
<div id="errorTitle">
|
|
||||||
<h1 class="errorTitleText" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- LONG CONTENT (the section most likely to require scrolling) -->
|
|
||||||
<div id="errorLongContent">
|
|
||||||
|
|
||||||
<!-- Short Description -->
|
|
||||||
<div id="errorShortDesc">
|
|
||||||
<p id="errorShortDescText" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
|
|
||||||
<div id="errorLongDesc" />
|
|
||||||
|
|
||||||
<!-- Override section - For ssl errors only. Removed on init for other
|
|
||||||
error types. -->
|
|
||||||
<div id="securityOverrideDiv">
|
|
||||||
<a id="securityOverrideLink" href="javascript:showSecuritySection();" >&securityOverride.linkText;</a>
|
|
||||||
<div id="securityOverrideContent" style="display: none;">&securityOverride.warningContent;</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Retry Button -->
|
|
||||||
<button id="errorTryAgain" onclick="retryThis(this);">&retry.label;</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
- Note: It is important to run the script this way, instead of using
|
|
||||||
- an onload handler. This is because error pages are loaded as
|
|
||||||
- LOAD_BACKGROUND, which means that onload handlers will not be executed.
|
|
||||||
-->
|
|
||||||
<script type="application/javascript">initPage();</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
#filter substitution
|
|
||||||
# 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/.
|
|
||||||
|
|
||||||
chrome.jar:
|
|
||||||
% content browser %content/ contentaccessible=yes
|
|
||||||
|
|
||||||
* content/about.xhtml (content/about.xhtml)
|
|
||||||
* content/about.js (content/about.js)
|
|
||||||
content/content.js (content/content.js)
|
|
||||||
content/aboutAddons.xhtml (content/aboutAddons.xhtml)
|
|
||||||
content/aboutAddons.js (content/aboutAddons.js)
|
|
||||||
content/aboutCertError.xhtml (content/aboutCertError.xhtml)
|
|
||||||
content/aboutDownloads.xhtml (content/aboutDownloads.xhtml)
|
|
||||||
content/aboutDownloads.js (content/aboutDownloads.js)
|
|
||||||
content/aboutPrivateBrowsing.xhtml (content/aboutPrivateBrowsing.xhtml)
|
|
||||||
content/aboutPrivateBrowsing.js (content/aboutPrivateBrowsing.js)
|
|
||||||
content/Reader.js (content/Reader.js)
|
|
||||||
content/aboutHome.xhtml (content/aboutHome.xhtml)
|
|
||||||
content/aboutRights.xhtml (content/aboutRights.xhtml)
|
|
||||||
content/blockedSite.xhtml (content/blockedSite.xhtml)
|
|
||||||
content/languages.properties (content/languages.properties)
|
|
||||||
content/browser.xul (content/browser.xul)
|
|
||||||
content/browser.css (content/browser.css)
|
|
||||||
content/browser.js (content/browser.js)
|
|
||||||
content/PresentationView.xul (content/PresentationView.xul)
|
|
||||||
content/PresentationView.js (content/PresentationView.js)
|
|
||||||
content/netError.xhtml (content/netError.xhtml)
|
|
||||||
content/EmbedRT.js (content/EmbedRT.js)
|
|
||||||
content/MemoryObserver.js (content/MemoryObserver.js)
|
|
||||||
content/ConsoleAPI.js (content/ConsoleAPI.js)
|
|
||||||
content/PrintHelper.js (content/PrintHelper.js)
|
|
||||||
content/OfflineApps.js (content/OfflineApps.js)
|
|
||||||
content/MasterPassword.js (content/MasterPassword.js)
|
|
||||||
content/FindHelper.js (content/FindHelper.js)
|
|
||||||
content/PermissionsHelper.js (content/PermissionsHelper.js)
|
|
||||||
content/FeedHandler.js (content/FeedHandler.js)
|
|
||||||
content/Feedback.js (content/Feedback.js)
|
|
||||||
content/Linkify.js (content/Linkify.js)
|
|
||||||
content/CastingApps.js (content/CastingApps.js)
|
|
||||||
content/RemoteDebugger.js (content/RemoteDebugger.js)
|
|
||||||
content/aboutAccounts.xhtml (content/aboutAccounts.xhtml)
|
|
||||||
content/aboutAccounts.js (content/aboutAccounts.js)
|
|
||||||
content/aboutExperiments.xhtml (content/aboutExperiments.xhtml)
|
|
||||||
content/aboutExperiments.js (content/aboutExperiments.js)
|
|
||||||
content/aboutLogins.xhtml (content/aboutLogins.xhtml)
|
|
||||||
content/aboutLogins.js (content/aboutLogins.js)
|
|
||||||
content/ExtensionPermissions.js (content/ExtensionPermissions.js)
|
|
||||||
|
|
||||||
% override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml
|
|
||||||
% override chrome://mozapps/content/extensions/extensions.xul chrome://browser/content/aboutAddons.xhtml
|
|
||||||
|
|
||||||
# L10n resource overrides.
|
|
||||||
% override chrome://global/locale/aboutReader.properties chrome://browser/locale/overrides/aboutReader.properties
|
|
||||||
% override chrome://global/locale/charsetMenu.properties chrome://browser/locale/overrides/charsetMenu.properties
|
|
||||||
% override chrome://global/locale/commonDialogs.properties chrome://browser/locale/overrides/commonDialogs.properties
|
|
||||||
% override chrome://global/locale/intl.properties chrome://browser/locale/overrides/intl.properties
|
|
||||||
% override chrome://global/locale/intl.css chrome://browser/locale/overrides/intl.css
|
|
||||||
% override chrome://global/locale/search/search.properties chrome://browser/locale/overrides/search/search.properties
|
|
||||||
% override chrome://pluginproblem/locale/pluginproblem.dtd chrome://browser/locale/overrides/plugins/pluginproblem.dtd
|
|
||||||
% override chrome://global/locale/mozilla.dtd chrome://browser/locale/overrides/global/mozilla.dtd
|
|
||||||
% override chrome://global/locale/aboutWebrtc.properties chrome://browser/locale/overrides/global/aboutWebrtc.properties
|
|
||||||
|
|
||||||
# overrides for dom l10n, also for en-US
|
|
||||||
# keep this file list in sync with filter.py
|
|
||||||
% override chrome://global/locale/global.dtd chrome://browser/locale/overrides/global.dtd
|
|
||||||
% override chrome://global/locale/AccessFu.properties chrome://browser/locale/overrides/AccessFu.properties
|
|
||||||
% override chrome://global/locale/dom/dom.properties chrome://browser/locale/overrides/dom/dom.properties
|
|
||||||
% override chrome://global/locale/plugins.properties chrome://browser/locale/overrides/plugins.properties
|
|
||||||
|
|
||||||
# mobile/locales/jar.mn resources and overrides
|
|
||||||
% override chrome://global/locale/netError.dtd chrome://browser/locale/netError.dtd
|
|
||||||
% override chrome://global/locale/appstrings.properties chrome://browser/locale/appstrings.properties
|
|
||||||
|
|
@ -18,5 +18,3 @@ DEFINES['PACKAGE'] = 'browser'
|
||||||
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
|
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
|
||||||
DEFINES['MOZ_APP_VERSION_DISPLAY'] = CONFIG['MOZ_APP_VERSION_DISPLAY']
|
DEFINES['MOZ_APP_VERSION_DISPLAY'] = CONFIG['MOZ_APP_VERSION_DISPLAY']
|
||||||
DEFINES['ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME']
|
DEFINES['ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME']
|
||||||
|
|
||||||
JAR_MANIFESTS += ['jar.mn']
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
|
||||||
# vim: set filetype=python:
|
|
||||||
# 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/.
|
|
||||||
|
|
||||||
with Files('**'):
|
|
||||||
BUG_COMPONENT = ('Firefox for Android', 'General')
|
|
||||||
|
|
||||||
DIRS += [
|
|
||||||
'webcompat'
|
|
||||||
]
|
|
||||||
if not CONFIG['MOZ_UPDATE_CHANNEL'] in ('release', 'esr'):
|
|
||||||
DIRS += [
|
|
||||||
'report-site-issue'
|
|
||||||
]
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
"rules": {
|
|
||||||
// Rules from the mozilla plugin
|
|
||||||
"mozilla/balanced-listeners": "error",
|
|
||||||
"mozilla/no-aArgs": "error",
|
|
||||||
"mozilla/var-only-at-top-level": "error",
|
|
||||||
|
|
||||||
"valid-jsdoc": ["error", {
|
|
||||||
"prefer": {
|
|
||||||
"return": "returns",
|
|
||||||
},
|
|
||||||
"preferType": {
|
|
||||||
"Boolean": "boolean",
|
|
||||||
"Number": "number",
|
|
||||||
"String": "string",
|
|
||||||
"bool": "boolean",
|
|
||||||
},
|
|
||||||
"requireParamDescription": false,
|
|
||||||
"requireReturn": false,
|
|
||||||
"requireReturnDescription": false,
|
|
||||||
}],
|
|
||||||
|
|
||||||
// No expressions where a statement is expected
|
|
||||||
"no-unused-expressions": "error",
|
|
||||||
|
|
||||||
// No declaring variables that are never used
|
|
||||||
"no-unused-vars": "error",
|
|
||||||
|
|
||||||
// Disallow using variables outside the blocks they are defined (especially
|
|
||||||
// since only let and const are used, see "no-var").
|
|
||||||
"block-scoped-var": "error",
|
|
||||||
|
|
||||||
// Warn about cyclomatic complexity in functions.
|
|
||||||
"complexity": ["error", {"max": 26}],
|
|
||||||
|
|
||||||
// Maximum depth callbacks can be nested.
|
|
||||||
"max-nested-callbacks": ["error", 4],
|
|
||||||
|
|
||||||
// Allow the console API aside from console.log.
|
|
||||||
"no-console": ["error", {allow: ["error", "info", "trace", "warn"]}],
|
|
||||||
|
|
||||||
// Disallow fallthrough of case statements, except if there is a comment.
|
|
||||||
"no-fallthrough": "error",
|
|
||||||
|
|
||||||
// Disallow use of multiline strings (use template strings instead).
|
|
||||||
"no-multi-str": "error",
|
|
||||||
|
|
||||||
// Disallow usage of __proto__ property.
|
|
||||||
"no-proto": "error",
|
|
||||||
|
|
||||||
// Disallow use of assignment in return statement. It is preferable for a
|
|
||||||
// single line of code to have only one easily predictable effect.
|
|
||||||
"no-return-assign": "error",
|
|
||||||
|
|
||||||
// Require use of the second argument for parseInt().
|
|
||||||
"radix": "error",
|
|
||||||
|
|
||||||
// Require "use strict" to be defined globally in the script.
|
|
||||||
"strict": ["error", "global"],
|
|
||||||
|
|
||||||
// Disallow Yoda conditions (where literal value comes first).
|
|
||||||
"yoda": "error",
|
|
||||||
|
|
||||||
// Disallow function or variable declarations in nested blocks
|
|
||||||
"no-inner-declarations": "error",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,306 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* globals browser */
|
|
||||||
|
|
||||||
const Config = {
|
|
||||||
newIssueEndpoint: "https://webcompat.com/issues/new",
|
|
||||||
newIssueEndpointPref: "newIssueEndpoint",
|
|
||||||
screenshotFormat: {
|
|
||||||
format: "jpeg",
|
|
||||||
quality: 75,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const FRAMEWORK_KEYS = ["hasFastClick", "hasMobify", "hasMarfeel"];
|
|
||||||
|
|
||||||
// If parental controls are on, we don't activate (that is, we don't show our
|
|
||||||
// menu item or prompt the user when they use "request desktop site".
|
|
||||||
browser.browserInfo.getParentalControlsEnabled().then(enabled => {
|
|
||||||
if (enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
browser.aboutConfigPrefs.onEndpointPrefChange.addListener(checkEndpointPref);
|
|
||||||
checkEndpointPref();
|
|
||||||
|
|
||||||
activateMenuItem();
|
|
||||||
activateDesktopViewPrompts();
|
|
||||||
});
|
|
||||||
|
|
||||||
function activateDesktopViewPrompts() {
|
|
||||||
Promise.all([
|
|
||||||
browser.l10n.getMessage("webcompat.reportDesktopMode.message"),
|
|
||||||
browser.l10n.getMessage("webcompat.reportDesktopModeYes.label"),
|
|
||||||
])
|
|
||||||
.then(([message, button]) => {
|
|
||||||
browser.tabExtras.onDesktopSiteRequested.addListener(async tabId => {
|
|
||||||
browser.tabs
|
|
||||||
.get(tabId)
|
|
||||||
.then(tab => {
|
|
||||||
browser.snackbars
|
|
||||||
.show(message, button)
|
|
||||||
.then(() => {
|
|
||||||
reportForTab(tab);
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
function activateMenuItem() {
|
|
||||||
browser.nativeMenu.show();
|
|
||||||
|
|
||||||
browser.l10n
|
|
||||||
.getMessage("webcompat.menu.name")
|
|
||||||
.then(label => {
|
|
||||||
// We use Fennec NativeMenus because its BrowserAction implementation
|
|
||||||
// lacks support for enabling/disabling its items.
|
|
||||||
browser.nativeMenu.setLabel(label);
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
|
|
||||||
browser.nativeMenu.onClicked.addListener(async () => {
|
|
||||||
const tabs = await browser.tabs.query({ active: true });
|
|
||||||
reportForTab(tabs[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function updateMenuItem(url) {
|
|
||||||
if (isReportableUrl(url)) {
|
|
||||||
await browser.nativeMenu.enable();
|
|
||||||
} else {
|
|
||||||
await browser.nativeMenu.disable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
|
||||||
if ("url" in changeInfo && tab.active) {
|
|
||||||
updateMenuItem(tab.url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.tabs.onActivated.addListener(({ tabId }) => {
|
|
||||||
browser.tabs
|
|
||||||
.get(tabId)
|
|
||||||
.then(({ url }) => {
|
|
||||||
updateMenuItem(url);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
updateMenuItem("about"); // So the action is disabled
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.tabs
|
|
||||||
.query({ active: true })
|
|
||||||
.then(tabs => {
|
|
||||||
updateMenuItem(tabs[0].url);
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
function isReportableUrl(url) {
|
|
||||||
return (
|
|
||||||
url &&
|
|
||||||
!(
|
|
||||||
url.startsWith("about") ||
|
|
||||||
url.startsWith("chrome") ||
|
|
||||||
url.startsWith("file") ||
|
|
||||||
url.startsWith("resource")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function reportForTab(tab) {
|
|
||||||
return getWebCompatInfoForTab(tab)
|
|
||||||
.then(async info => {
|
|
||||||
return openWebCompatTab(info, tab.incognito);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error("Report Site Issue: unexpected error", err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkEndpointPref() {
|
|
||||||
const value = await browser.aboutConfigPrefs.getEndpointPref();
|
|
||||||
if (value === undefined) {
|
|
||||||
browser.aboutConfigPrefs.setEndpointPref(Config.newIssueEndpoint);
|
|
||||||
} else {
|
|
||||||
Config.newIssueEndpoint = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasFastClickPageScript() {
|
|
||||||
const win = window.wrappedJSObject;
|
|
||||||
|
|
||||||
if (win.FastClick) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const property in win) {
|
|
||||||
try {
|
|
||||||
const proto = win[property].prototype;
|
|
||||||
if (proto && proto.needsClick) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasMobifyPageScript() {
|
|
||||||
const win = window.wrappedJSObject;
|
|
||||||
return !!(win.Mobify && win.Mobify.Tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasMarfeelPageScript() {
|
|
||||||
const win = window.wrappedJSObject;
|
|
||||||
return !!win.marfeel;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkForFrameworks(tabId) {
|
|
||||||
return browser.tabs
|
|
||||||
.executeScript(tabId, {
|
|
||||||
code: `
|
|
||||||
(function() {
|
|
||||||
${hasFastClickPageScript};
|
|
||||||
${hasMobifyPageScript};
|
|
||||||
${hasMarfeelPageScript};
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
hasFastClick: hasFastClickPageScript(),
|
|
||||||
hasMobify: hasMobifyPageScript(),
|
|
||||||
hasMarfeel: hasMarfeelPageScript(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
})();
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
.then(([results]) => results)
|
|
||||||
.catch(() => false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getWebCompatInfoForTab(tab) {
|
|
||||||
const { id, windiwId, url } = tab;
|
|
||||||
return Promise.all([
|
|
||||||
browser.browserInfo.getBlockList(),
|
|
||||||
browser.browserInfo.getBuildID(),
|
|
||||||
browser.browserInfo.getGraphicsPrefs(),
|
|
||||||
browser.browserInfo.getUpdateChannel(),
|
|
||||||
browser.browserInfo.hasTouchScreen(),
|
|
||||||
browser.tabExtras.getWebcompatInfo(id),
|
|
||||||
checkForFrameworks(id),
|
|
||||||
browser.tabs
|
|
||||||
.captureVisibleTab(windiwId, Config.screenshotFormat)
|
|
||||||
.catch(e => {
|
|
||||||
console.error("Report Site Issue: getting a screenshot failed", e);
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
}),
|
|
||||||
]).then(
|
|
||||||
([
|
|
||||||
blockList,
|
|
||||||
buildID,
|
|
||||||
graphicsPrefs,
|
|
||||||
channel,
|
|
||||||
hasTouchScreen,
|
|
||||||
frameInfo,
|
|
||||||
frameworks,
|
|
||||||
screenshot,
|
|
||||||
]) => {
|
|
||||||
if (channel !== "linux") {
|
|
||||||
delete graphicsPrefs["layers.acceleration.force-enabled"];
|
|
||||||
}
|
|
||||||
|
|
||||||
const consoleLog = frameInfo.log;
|
|
||||||
delete frameInfo.log;
|
|
||||||
|
|
||||||
return Object.assign(frameInfo, {
|
|
||||||
tabId: id,
|
|
||||||
blockList,
|
|
||||||
details: Object.assign(graphicsPrefs, {
|
|
||||||
buildID,
|
|
||||||
channel,
|
|
||||||
consoleLog,
|
|
||||||
frameworks,
|
|
||||||
hasTouchScreen,
|
|
||||||
"mixed active content blocked":
|
|
||||||
frameInfo.hasMixedActiveContentBlocked,
|
|
||||||
"mixed passive content blocked":
|
|
||||||
frameInfo.hasMixedDisplayContentBlocked,
|
|
||||||
"tracking content blocked": frameInfo.hasTrackingContentBlocked
|
|
||||||
? `true (${blockList})`
|
|
||||||
: "false",
|
|
||||||
}),
|
|
||||||
screenshot,
|
|
||||||
url,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripNonASCIIChars(str) {
|
|
||||||
// eslint-disable-next-line no-control-regex
|
|
||||||
return str.replace(/[^\x00-\x7F]/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openWebCompatTab(compatInfo, usePrivateTab) {
|
|
||||||
const url = new URL(Config.newIssueEndpoint);
|
|
||||||
const { details } = compatInfo;
|
|
||||||
const params = {
|
|
||||||
url: `${compatInfo.url}`,
|
|
||||||
utm_source: "mobile-reporter",
|
|
||||||
utm_campaign: "report-site-issue-button",
|
|
||||||
src: "mobile-reporter",
|
|
||||||
details,
|
|
||||||
extra_labels: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let framework of FRAMEWORK_KEYS) {
|
|
||||||
if (details.frameworks[framework]) {
|
|
||||||
params.details[framework] = true;
|
|
||||||
params.extra_labels.push(
|
|
||||||
framework.replace(/^has/, "type-").toLowerCase()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete details.frameworks;
|
|
||||||
|
|
||||||
if (details["gfx.webrender.all"] || details["gfx.webrender.enabled"]) {
|
|
||||||
params.extra_labels.push("type-webrender-enabled");
|
|
||||||
}
|
|
||||||
if (compatInfo.hasTrackingContentBlocked) {
|
|
||||||
params.extra_labels.push(
|
|
||||||
`type-tracking-protection-${compatInfo.blockList}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Need custom API for private tabs until https://bugzil.la/1372178 is fixed
|
|
||||||
const tab = usePrivateTab
|
|
||||||
? await browser.tabExtras.createPrivateTab()
|
|
||||||
: await browser.tabs.create({ url: "about:blank" });
|
|
||||||
const json = stripNonASCIIChars(JSON.stringify(params));
|
|
||||||
await browser.tabExtras.loadURIWithPostData(
|
|
||||||
tab.id,
|
|
||||||
url.href,
|
|
||||||
json,
|
|
||||||
"application/json"
|
|
||||||
);
|
|
||||||
await browser.tabs.executeScript(tab.id, {
|
|
||||||
runAt: "document_end",
|
|
||||||
code: `(function() {
|
|
||||||
async function sendScreenshot(dataURI) {
|
|
||||||
const res = await fetch(dataURI);
|
|
||||||
const blob = await res.blob();
|
|
||||||
postMessage(blob, "${url.origin}");
|
|
||||||
}
|
|
||||||
sendScreenshot("${compatInfo.screenshot}");
|
|
||||||
})()`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* global ExtensionAPI, ExtensionCommon */
|
|
||||||
|
|
||||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
|
|
||||||
this.aboutConfigPrefs = class extends ExtensionAPI {
|
|
||||||
getAPI(context) {
|
|
||||||
const EventManager = ExtensionCommon.EventManager;
|
|
||||||
const extensionIDBase = context.extension.id.split("@")[0];
|
|
||||||
const endpointPrefName = `extensions.${extensionIDBase}.newIssueEndpoint`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
aboutConfigPrefs: {
|
|
||||||
onEndpointPrefChange: new EventManager({
|
|
||||||
context,
|
|
||||||
name: "aboutConfigPrefs.onEndpointPrefChange",
|
|
||||||
register: fire => {
|
|
||||||
const callback = () => {
|
|
||||||
fire.async().catch(() => {}); // ignore Message Manager disconnects
|
|
||||||
};
|
|
||||||
Services.prefs.addObserver(endpointPrefName, callback);
|
|
||||||
return () => {
|
|
||||||
Services.prefs.removeObserver(endpointPrefName, callback);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}).api(),
|
|
||||||
async getEndpointPref() {
|
|
||||||
return Services.prefs.getStringPref(endpointPrefName, undefined);
|
|
||||||
},
|
|
||||||
async setEndpointPref(value) {
|
|
||||||
Services.prefs.setStringPref(endpointPrefName, value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"namespace": "aboutConfigPrefs",
|
|
||||||
"description": "experimental API extension to allow access to about:config preferences",
|
|
||||||
"events": [
|
|
||||||
{
|
|
||||||
"name": "onEndpointPrefChange",
|
|
||||||
"type": "function",
|
|
||||||
"parameters": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"functions": [
|
|
||||||
{
|
|
||||||
"name": "getEndpointPref",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Get the endpoint preference's value",
|
|
||||||
"parameters": [],
|
|
||||||
"async": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "setEndpointPref",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Set the endpoint preference's value",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "value",
|
|
||||||
"type": "string",
|
|
||||||
"description": "The new value"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"async": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* global ExtensionAPI */
|
|
||||||
|
|
||||||
var { AppConstants } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/AppConstants.jsm"
|
|
||||||
);
|
|
||||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
|
|
||||||
const gParentalControls = (function() {
|
|
||||||
if ("@mozilla.org/parental-controls-service;1" in Cc) {
|
|
||||||
return Cc["@mozilla.org/parental-controls-service;1"].createInstance(
|
|
||||||
Ci.nsIParentalControlsService
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return { parentalControlsEnabled: false };
|
|
||||||
})();
|
|
||||||
|
|
||||||
this.browserInfo = class extends ExtensionAPI {
|
|
||||||
getAPI(context) {
|
|
||||||
return {
|
|
||||||
browserInfo: {
|
|
||||||
async getGraphicsPrefs() {
|
|
||||||
const prefs = {};
|
|
||||||
for (const [name, dflt] of Object.entries({
|
|
||||||
"layers.acceleration.force-enabled": false,
|
|
||||||
"gfx.webrender.all": false,
|
|
||||||
"gfx.webrender.blob-images": true,
|
|
||||||
"gfx.webrender.enabled": false,
|
|
||||||
"image.mem.shared": true,
|
|
||||||
})) {
|
|
||||||
prefs[name] = Services.prefs.getBoolPref(name, dflt);
|
|
||||||
}
|
|
||||||
return prefs;
|
|
||||||
},
|
|
||||||
async getAppVersion() {
|
|
||||||
return AppConstants.MOZ_APP_VERSION;
|
|
||||||
},
|
|
||||||
async getBlockList() {
|
|
||||||
const trackingTable = Services.prefs.getCharPref(
|
|
||||||
"urlclassifier.trackingTable"
|
|
||||||
);
|
|
||||||
// If content-track-digest256 is in the tracking table,
|
|
||||||
// the user has enabled the strict list.
|
|
||||||
return trackingTable.includes("content") ? "strict" : "basic";
|
|
||||||
},
|
|
||||||
async getBuildID() {
|
|
||||||
return Services.appinfo.appBuildID;
|
|
||||||
},
|
|
||||||
async getUpdateChannel() {
|
|
||||||
return AppConstants.MOZ_UPDATE_CHANNEL;
|
|
||||||
},
|
|
||||||
async getParentalControlsEnabled() {
|
|
||||||
return gParentalControls.parentalControlsEnabled;
|
|
||||||
},
|
|
||||||
async getPlatform() {
|
|
||||||
return AppConstants.platform;
|
|
||||||
},
|
|
||||||
async hasTouchScreen() {
|
|
||||||
const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(
|
|
||||||
Ci.nsIGfxInfo
|
|
||||||
);
|
|
||||||
return gfxInfo.getInfo().ApzTouchInput == 1;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"namespace": "browserInfo",
|
|
||||||
"description": "experimental API extensions to get browser info not exposed via web APIs",
|
|
||||||
"functions": [
|
|
||||||
{
|
|
||||||
"name": "getAppVersion",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Gets the app version",
|
|
||||||
"parameters": [],
|
|
||||||
"async": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "getBlockList",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Gets the current blocklist",
|
|
||||||
"parameters": [],
|
|
||||||
"async": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "getBuildID",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Gets the build ID",
|
|
||||||
"parameters": [],
|
|
||||||
"async": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "getGraphicsPrefs",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Gets interesting about:config prefs for graphics",
|
|
||||||
"parameters": [],
|
|
||||||
"async": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "getParentalControlsEnabled",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Gets whether the Parent Controls are enabled",
|
|
||||||
"parameters": [],
|
|
||||||
"async": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "getPlatform",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Gets the platform",
|
|
||||||
"parameters": [],
|
|
||||||
"async": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "getUpdateChannel",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Gets the update channel",
|
|
||||||
"parameters": [],
|
|
||||||
"async": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hasTouchScreen",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Gets whether a touchscreen is present",
|
|
||||||
"parameters": [],
|
|
||||||
"async": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* global ExtensionAPI, XPCOMUtils */
|
|
||||||
|
|
||||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "l10nStrings", function() {
|
|
||||||
return Services.strings.createBundle(
|
|
||||||
"chrome://browser/locale/webcompatReporter.properties"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
let l10nManifest;
|
|
||||||
|
|
||||||
this.l10n = class extends ExtensionAPI {
|
|
||||||
getAPI(context) {
|
|
||||||
return {
|
|
||||||
l10n: {
|
|
||||||
getMessage(name) {
|
|
||||||
try {
|
|
||||||
return Promise.resolve(l10nStrings.GetStringFromName(name));
|
|
||||||
} catch (e) {
|
|
||||||
return Promise.reject(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"namespace": "l10n",
|
|
||||||
"description": "A stop-gap L10N API only meant to be used until a Fluent-based API is added in bug 1425104",
|
|
||||||
"functions": [
|
|
||||||
{
|
|
||||||
"name": "getMessage",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Gets the message with the given name",
|
|
||||||
"parameters": [{
|
|
||||||
"name": "name",
|
|
||||||
"type": "string",
|
|
||||||
"description": "The name of the message"
|
|
||||||
}],
|
|
||||||
"async": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* global ExtensionAPI, ExtensionCommon */
|
|
||||||
|
|
||||||
const {
|
|
||||||
Management: {
|
|
||||||
global: { windowTracker },
|
|
||||||
},
|
|
||||||
} = ChromeUtils.import("resource://gre/modules/Extension.jsm", null);
|
|
||||||
|
|
||||||
function getNativeWindow() {
|
|
||||||
return windowTracker.topWindow.NativeWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
const clickHandlers = new ExtensionCommon.EventEmitter();
|
|
||||||
|
|
||||||
const menuItem = getNativeWindow().menu.add({
|
|
||||||
name: "Report site issue",
|
|
||||||
callback: () => {
|
|
||||||
clickHandlers.emit("click");
|
|
||||||
},
|
|
||||||
enabled: false,
|
|
||||||
visible: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.nativeMenu = class extends ExtensionAPI {
|
|
||||||
getAPI(context) {
|
|
||||||
return {
|
|
||||||
nativeMenu: {
|
|
||||||
onClicked: new ExtensionCommon.EventManager({
|
|
||||||
context,
|
|
||||||
name: "nativeMenu.onClicked",
|
|
||||||
register: fire => {
|
|
||||||
const callback = () => {
|
|
||||||
fire.async().catch(() => {}); // ignore Message Manager disconnects
|
|
||||||
};
|
|
||||||
clickHandlers.on("click", callback);
|
|
||||||
return () => {
|
|
||||||
clickHandlers.off("click", callback);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}).api(),
|
|
||||||
async disable() {
|
|
||||||
getNativeWindow().menu.update(menuItem, { enabled: false });
|
|
||||||
},
|
|
||||||
async enable() {
|
|
||||||
getNativeWindow().menu.update(menuItem, { enabled: true });
|
|
||||||
},
|
|
||||||
async hide() {
|
|
||||||
getNativeWindow().menu.update(menuItem, { visible: false });
|
|
||||||
},
|
|
||||||
async show() {
|
|
||||||
getNativeWindow().menu.update(menuItem, { visible: true });
|
|
||||||
},
|
|
||||||
async setLabel(label) {
|
|
||||||
getNativeWindow().menu.update(menuItem, { name: label });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"namespace": "nativeMenu",
|
|
||||||
"description": "experimental extension wrapping around a Fennec NativeMenu",
|
|
||||||
"events": [
|
|
||||||
{
|
|
||||||
"name": "onClicked",
|
|
||||||
"type": "function",
|
|
||||||
"parameters": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"functions": [
|
|
||||||
{
|
|
||||||
"name": "disable",
|
|
||||||
"type": "function",
|
|
||||||
"async": true,
|
|
||||||
"description": "Disable the addon's menu item",
|
|
||||||
"parameters": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "enable",
|
|
||||||
"type": "function",
|
|
||||||
"async": true,
|
|
||||||
"description": "Enable the addon's menu item",
|
|
||||||
"parameters": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hide",
|
|
||||||
"type": "function",
|
|
||||||
"async": true,
|
|
||||||
"description": "Hide the addon's menu item",
|
|
||||||
"parameters": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "show",
|
|
||||||
"type": "function",
|
|
||||||
"async": true,
|
|
||||||
"description": "Show the addon's menu item",
|
|
||||||
"parameters": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "setLabel",
|
|
||||||
"type": "function",
|
|
||||||
"async": true,
|
|
||||||
"description": "Set the label of the addon's menu item",
|
|
||||||
"parameters": [{
|
|
||||||
"name": "label",
|
|
||||||
"type": "string"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* global ExtensionAPI */
|
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"clearTimeout",
|
|
||||||
"resource://gre/modules/Timer.jsm"
|
|
||||||
);
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"setTimeout",
|
|
||||||
"resource://gre/modules/Timer.jsm"
|
|
||||||
);
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"Snackbars",
|
|
||||||
"resource://gre/modules/Snackbars.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
this.snackbars = class extends ExtensionAPI {
|
|
||||||
getAPI(context) {
|
|
||||||
return {
|
|
||||||
snackbars: {
|
|
||||||
show(message, button) {
|
|
||||||
return new Promise((callback, rejection) => {
|
|
||||||
Snackbars.show(message, Snackbars.LENGTH_LONG, {
|
|
||||||
action: {
|
|
||||||
label: button,
|
|
||||||
callback,
|
|
||||||
rejection,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"namespace": "snackbars",
|
|
||||||
"description": "experimental API extensions for prompting the user via Android Snackbar notifications",
|
|
||||||
"functions": [
|
|
||||||
{
|
|
||||||
"name": "show",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Shows a Snackbar with the given message and button",
|
|
||||||
"parameters": [{
|
|
||||||
"name": "message",
|
|
||||||
"type": "string"
|
|
||||||
},{
|
|
||||||
"name": "button",
|
|
||||||
"type": "string"
|
|
||||||
}],
|
|
||||||
"async": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,327 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* global ChromeUtils, ExtensionAPI, ExtensionCommon, XPCOMUtils */
|
|
||||||
|
|
||||||
var Services;
|
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"EventDispatcher",
|
|
||||||
"resource://gre/modules/Messaging.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"PrivateBrowsingUtils",
|
|
||||||
"resource://gre/modules/PrivateBrowsingUtils.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(
|
|
||||||
this,
|
|
||||||
"GlobalEventDispatcher",
|
|
||||||
() => EventDispatcher.instance
|
|
||||||
);
|
|
||||||
|
|
||||||
function getInfoFrameScript(messageName) {
|
|
||||||
/* eslint-env mozilla/frame-script */
|
|
||||||
|
|
||||||
({ Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"));
|
|
||||||
const PREVIEW_MAX_ITEMS = 10;
|
|
||||||
const LOG_LEVEL_MAP = {
|
|
||||||
0: "debug",
|
|
||||||
1: "info",
|
|
||||||
2: "warn",
|
|
||||||
3: "error",
|
|
||||||
};
|
|
||||||
|
|
||||||
function getInnerWindowId(window) {
|
|
||||||
return window.windowUtils.currentInnerWindowID;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInnerWindowIDsForAllFrames(window) {
|
|
||||||
const innerWindowID = getInnerWindowId(window);
|
|
||||||
let ids = [innerWindowID];
|
|
||||||
|
|
||||||
if (window.frames) {
|
|
||||||
for (let i = 0; i < window.frames.length; i++) {
|
|
||||||
ids = ids.concat(getInnerWindowIDsForAllFrames(window.frames[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLoggedMessages(window, includePrivate = false) {
|
|
||||||
const ids = getInnerWindowIDsForAllFrames(window);
|
|
||||||
return getConsoleMessages(ids)
|
|
||||||
.concat(getScriptErrors(ids, includePrivate))
|
|
||||||
.sort((a, b) => a.timeStamp - b.timeStamp)
|
|
||||||
.map(m => m.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPreview(value) {
|
|
||||||
switch (typeof value) {
|
|
||||||
case "function":
|
|
||||||
return "function ()";
|
|
||||||
|
|
||||||
case "object":
|
|
||||||
if (value === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return `(${value.length})[...]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "{...}";
|
|
||||||
|
|
||||||
case "undefined":
|
|
||||||
return "undefined";
|
|
||||||
|
|
||||||
default:
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getArrayPreview(arr) {
|
|
||||||
const preview = [];
|
|
||||||
for (const value of arr) {
|
|
||||||
preview.push(getPreview(value));
|
|
||||||
|
|
||||||
if (preview.length === PREVIEW_MAX_ITEMS) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return preview;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getObjectPreview(obj) {
|
|
||||||
const preview = {};
|
|
||||||
for (const key in obj) {
|
|
||||||
if (obj.hasOwnProperty(key)) {
|
|
||||||
preview[key] = getPreview(obj[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.keys(preview).length === PREVIEW_MAX_ITEMS) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return preview;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getArgs(value) {
|
|
||||||
if (typeof value === "object" && value !== null) {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return getArrayPreview(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getObjectPreview(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getPreview(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConsoleMessages(windowIds) {
|
|
||||||
const ConsoleAPIStorage = Cc[
|
|
||||||
"@mozilla.org/consoleAPI-storage;1"
|
|
||||||
].getService(Ci.nsIConsoleAPIStorage);
|
|
||||||
let messages = [];
|
|
||||||
for (const id of windowIds) {
|
|
||||||
messages = messages.concat(ConsoleAPIStorage.getEvents(id) || []);
|
|
||||||
}
|
|
||||||
return messages.map(evt => {
|
|
||||||
const { columnNumber, filename, level, lineNumber, timeStamp } = evt;
|
|
||||||
const args = evt.arguments.map(getArgs);
|
|
||||||
|
|
||||||
const message = {
|
|
||||||
level,
|
|
||||||
log: args,
|
|
||||||
uri: filename,
|
|
||||||
pos: `${lineNumber}:${columnNumber}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return { timeStamp, message };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScriptErrors(windowIds, includePrivate = false) {
|
|
||||||
const messages = Services.console.getMessageArray() || [];
|
|
||||||
return messages
|
|
||||||
.filter(message => {
|
|
||||||
if (message instanceof Ci.nsIScriptError) {
|
|
||||||
if (!includePrivate && message.isFromPrivateWindow) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (windowIds && !windowIds.includes(message.innerWindowID)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is not an nsIScriptError and we need to do window-based
|
|
||||||
// filtering we skip this message.
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
.map(error => {
|
|
||||||
const {
|
|
||||||
timeStamp,
|
|
||||||
errorMessage,
|
|
||||||
sourceName,
|
|
||||||
lineNumber,
|
|
||||||
columnNumber,
|
|
||||||
logLevel,
|
|
||||||
} = error;
|
|
||||||
const message = {
|
|
||||||
level: LOG_LEVEL_MAP[logLevel],
|
|
||||||
log: [errorMessage],
|
|
||||||
uri: sourceName,
|
|
||||||
pos: `${lineNumber}:${columnNumber}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return { timeStamp, message };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
sendAsyncMessage(messageName, {
|
|
||||||
hasMixedActiveContentBlocked: docShell.hasMixedActiveContentBlocked,
|
|
||||||
hasMixedDisplayContentBlocked: docShell.hasMixedDisplayContentBlocked,
|
|
||||||
hasTrackingContentBlocked: docShell.hasTrackingContentBlocked,
|
|
||||||
log: getLoggedMessages(content, true), // also on private tabs
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tabExtras = class extends ExtensionAPI {
|
|
||||||
getAPI(context) {
|
|
||||||
const EventManager = ExtensionCommon.EventManager;
|
|
||||||
const { tabManager } = context.extension;
|
|
||||||
const {
|
|
||||||
Management: {
|
|
||||||
global: { windowTracker },
|
|
||||||
},
|
|
||||||
} = ChromeUtils.import("resource://gre/modules/Extension.jsm", null);
|
|
||||||
return {
|
|
||||||
tabExtras: {
|
|
||||||
onDesktopSiteRequested: new EventManager({
|
|
||||||
context,
|
|
||||||
name: "tabExtras.onDesktopSiteRequested",
|
|
||||||
register: fire => {
|
|
||||||
const callback = tab => {
|
|
||||||
fire.async(tab).catch(() => {}); // ignore Message Manager disconnects
|
|
||||||
};
|
|
||||||
const listener = {
|
|
||||||
onEvent: (event, data, _callback) => {
|
|
||||||
if (event === "DesktopMode:Change" && data.desktopMode) {
|
|
||||||
callback(data.tabId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
GlobalEventDispatcher.registerListener(
|
|
||||||
listener,
|
|
||||||
"DesktopMode:Change"
|
|
||||||
);
|
|
||||||
return () => {
|
|
||||||
GlobalEventDispatcher.unregisterListener(
|
|
||||||
listener,
|
|
||||||
"DesktopMode:Change"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}).api(),
|
|
||||||
async createPrivateTab() {
|
|
||||||
const { BrowserApp } = windowTracker.topWindow;
|
|
||||||
const nativeTab = BrowserApp.addTab("about:blank", {
|
|
||||||
selected: true,
|
|
||||||
isPrivate: true,
|
|
||||||
});
|
|
||||||
return Promise.resolve(tabManager.convert(nativeTab));
|
|
||||||
},
|
|
||||||
async loadURIWithPostData(
|
|
||||||
tabId,
|
|
||||||
url,
|
|
||||||
postDataString,
|
|
||||||
postDataContentType
|
|
||||||
) {
|
|
||||||
const tab = tabManager.get(tabId);
|
|
||||||
if (!tab || !tab.browser) {
|
|
||||||
return Promise.reject("Invalid tab");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
new URL(url);
|
|
||||||
} catch (_) {
|
|
||||||
return Promise.reject("Invalid url");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof postDataString !== "string" &&
|
|
||||||
!(postDataString instanceof String)
|
|
||||||
) {
|
|
||||||
return Promise.reject("postDataString must be a string");
|
|
||||||
}
|
|
||||||
|
|
||||||
const stringStream = Cc[
|
|
||||||
"@mozilla.org/io/string-input-stream;1"
|
|
||||||
].createInstance(Ci.nsIStringInputStream);
|
|
||||||
stringStream.data = postData;
|
|
||||||
const postData = Cc[
|
|
||||||
"@mozilla.org/network/mime-input-stream;1"
|
|
||||||
].createInstance(Ci.nsIMIMEInputStream);
|
|
||||||
postData.addHeader(
|
|
||||||
"Content-Type",
|
|
||||||
postDataContentType || "application/x-www-form-urlencoded"
|
|
||||||
);
|
|
||||||
postData.setData(stringStream);
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const listener = {
|
|
||||||
onLocationChange(
|
|
||||||
browser,
|
|
||||||
webProgress,
|
|
||||||
request,
|
|
||||||
locationURI,
|
|
||||||
flags
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
webProgress.isTopLevel &&
|
|
||||||
browser === tab.browser &&
|
|
||||||
locationURI.spec === url
|
|
||||||
) {
|
|
||||||
windowTracker.removeListener("progress", listener);
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
windowTracker.addListener("progress", listener);
|
|
||||||
let loadURIOptions = {
|
|
||||||
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
|
|
||||||
postData,
|
|
||||||
};
|
|
||||||
tab.browser.webNavigation.loadURI(url, loadURIOptions);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async getWebcompatInfo(tabId) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const messageName = "WebExtension:GetWebcompatInfo";
|
|
||||||
const code = `${getInfoFrameScript.toString()};getInfoFrameScript("${messageName}")`;
|
|
||||||
const mm = tabManager.get(tabId).browser.messageManager;
|
|
||||||
mm.loadFrameScript(`data:,${encodeURI(code)}`, false);
|
|
||||||
mm.addMessageListener(messageName, function receiveFn(message) {
|
|
||||||
mm.removeMessageListener(messageName, receiveFn);
|
|
||||||
resolve(message.json);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"namespace": "tabExtras",
|
|
||||||
"description": "experimental tab API extensions",
|
|
||||||
"events": [{
|
|
||||||
"name": "onDesktopSiteRequested",
|
|
||||||
"type": "function",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "tabId",
|
|
||||||
"type": "integer",
|
|
||||||
"description": "The related tab's id"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
"functions": [
|
|
||||||
{
|
|
||||||
"name": "createPrivateTab",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Create and select a new private about:blank tab",
|
|
||||||
"parameters": [],
|
|
||||||
"async": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "getWebcompatInfo",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Gets the content blocking status and script log for a given tab",
|
|
||||||
"parameters": [{
|
|
||||||
"type": "integer",
|
|
||||||
"name": "tabId",
|
|
||||||
"minimum": 0
|
|
||||||
}],
|
|
||||||
"async": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "loadURIWithPostData",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Loads a URI on the given tab using a POST request",
|
|
||||||
"parameters": [{
|
|
||||||
"type": "integer",
|
|
||||||
"name": "tabId",
|
|
||||||
"minimum": 0
|
|
||||||
}, {
|
|
||||||
"type": "string",
|
|
||||||
"name": "url"
|
|
||||||
}, {
|
|
||||||
"type": "string",
|
|
||||||
"name": "postData"
|
|
||||||
}, {
|
|
||||||
"type": "string",
|
|
||||||
"name": "postDataContentType"
|
|
||||||
}],
|
|
||||||
"async": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
{
|
|
||||||
"manifest_version": 2,
|
|
||||||
"name": "WebCompat Reporter",
|
|
||||||
"description": "Report site compatibility issues on webcompat.com",
|
|
||||||
"author": "Thomas Wisniewski <twisniewski@mozilla.com>",
|
|
||||||
"version": "1.1.0",
|
|
||||||
"homepage_url": "https://github.com/mozilla/webcompat-reporter",
|
|
||||||
"applications": {
|
|
||||||
"gecko": {
|
|
||||||
"id": "webcompat-reporter@mozilla.org"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"experiment_apis": {
|
|
||||||
"aboutConfigPrefs": {
|
|
||||||
"schema": "experimentalAPIs/aboutConfigPrefs.json",
|
|
||||||
"parent": {
|
|
||||||
"scopes": ["addon_parent"],
|
|
||||||
"script": "experimentalAPIs/aboutConfigPrefs.js",
|
|
||||||
"paths": [["aboutConfigPrefs"]]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"browserInfo": {
|
|
||||||
"schema": "experimentalAPIs/browserInfo.json",
|
|
||||||
"parent": {
|
|
||||||
"scopes": ["addon_parent"],
|
|
||||||
"script": "experimentalAPIs/browserInfo.js",
|
|
||||||
"paths": [["browserInfo"]]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"l10n": {
|
|
||||||
"schema": "experimentalAPIs/l10n.json",
|
|
||||||
"parent": {
|
|
||||||
"scopes": ["addon_parent"],
|
|
||||||
"script": "experimentalAPIs/l10n.js",
|
|
||||||
"paths": [["l10n"]]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nativeMenu": {
|
|
||||||
"schema": "experimentalAPIs/nativeMenu.json",
|
|
||||||
"parent": {
|
|
||||||
"scopes": ["addon_parent"],
|
|
||||||
"script": "experimentalAPIs/nativeMenu.js",
|
|
||||||
"paths": [["nativeMenu"]]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"snackbars": {
|
|
||||||
"schema": "experimentalAPIs/snackbars.json",
|
|
||||||
"parent": {
|
|
||||||
"scopes": ["addon_parent"],
|
|
||||||
"script": "experimentalAPIs/snackbars.js",
|
|
||||||
"paths": [["snackbars"]]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tabExtras": {
|
|
||||||
"schema": "experimentalAPIs/tabExtras.json",
|
|
||||||
"parent": {
|
|
||||||
"scopes": ["addon_parent"],
|
|
||||||
"script": "experimentalAPIs/tabExtras.js",
|
|
||||||
"paths": [["tabExtras"]]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"permissions": [
|
|
||||||
"tabs",
|
|
||||||
"<all_urls>"
|
|
||||||
],
|
|
||||||
"background": {
|
|
||||||
"scripts": [
|
|
||||||
"background.js"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
|
||||||
# vim: set filetype=python:
|
|
||||||
# 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/.
|
|
||||||
|
|
||||||
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
|
|
||||||
DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
|
|
||||||
|
|
||||||
FINAL_TARGET_FILES.features['webcompat-reporter@mozilla.org'] += [
|
|
||||||
'background.js',
|
|
||||||
'manifest.json'
|
|
||||||
]
|
|
||||||
|
|
||||||
FINAL_TARGET_FILES.features['webcompat-reporter@mozilla.org'].experimentalAPIs += [
|
|
||||||
'experimentalAPIs/aboutConfigPrefs.js',
|
|
||||||
'experimentalAPIs/aboutConfigPrefs.json',
|
|
||||||
'experimentalAPIs/browserInfo.js',
|
|
||||||
'experimentalAPIs/browserInfo.json',
|
|
||||||
'experimentalAPIs/l10n.js',
|
|
||||||
'experimentalAPIs/l10n.json',
|
|
||||||
'experimentalAPIs/nativeMenu.js',
|
|
||||||
'experimentalAPIs/nativeMenu.json',
|
|
||||||
'experimentalAPIs/snackbars.js',
|
|
||||||
'experimentalAPIs/snackbars.json',
|
|
||||||
'experimentalAPIs/tabExtras.js',
|
|
||||||
'experimentalAPIs/tabExtras.json'
|
|
||||||
]
|
|
||||||
|
|
||||||
with Files('**'):
|
|
||||||
BUG_COMPONENT = ('Web Compatibility Tools', 'General')
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["AboutCompat"];
|
|
||||||
|
|
||||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
||||||
|
|
||||||
const addonID = "webcompat@mozilla.org";
|
|
||||||
const addonPageRelativeURL = "/about-compat/aboutCompat.html";
|
|
||||||
|
|
||||||
function AboutCompat() {
|
|
||||||
this.chromeURL = WebExtensionPolicy.getByID(addonID).getURL(
|
|
||||||
addonPageRelativeURL
|
|
||||||
);
|
|
||||||
}
|
|
||||||
AboutCompat.prototype = {
|
|
||||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIAboutModule]),
|
|
||||||
getURIFlags() {
|
|
||||||
return Ci.nsIAboutModule.URI_MUST_LOAD_IN_EXTENSION_PROCESS;
|
|
||||||
},
|
|
||||||
|
|
||||||
newChannel(aURI, aLoadInfo) {
|
|
||||||
const uri = Services.io.newURI(this.chromeURL);
|
|
||||||
const channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
|
|
||||||
channel.originalURI = aURI;
|
|
||||||
|
|
||||||
channel.owner = (Services.scriptSecurityManager.createContentPrincipal ||
|
|
||||||
Services.scriptSecurityManager.createCodebasePrincipal)(
|
|
||||||
uri,
|
|
||||||
aLoadInfo.originAttributes
|
|
||||||
);
|
|
||||||
return channel;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -1,198 +0,0 @@
|
||||||
@media (any-pointer: fine) {
|
|
||||||
:root {
|
|
||||||
font-family: sans-serif;
|
|
||||||
margin: 40px auto;
|
|
||||||
min-width: 30em;
|
|
||||||
max-width: 60em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
padding-bottom: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-title-container {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wide-button {
|
|
||||||
display: block;
|
|
||||||
min-height: 32px;
|
|
||||||
padding-left: 30px;
|
|
||||||
padding-right: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submitting {
|
|
||||||
background-image: url(chrome://global/skin/icons/loading.png);
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submitting .submit-crash-button-label {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.failed-to-submit {
|
|
||||||
color: #ca8695;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.button-as-link {
|
|
||||||
-moz-appearance: none;
|
|
||||||
min-height: 30px;
|
|
||||||
color: var(--in-content-text-color) !important;
|
|
||||||
border: 1px solid var(--in-content-box-border-color) !important;
|
|
||||||
border-radius: 2px;
|
|
||||||
background-color: var(--in-content-page-background);
|
|
||||||
line-height: 30px;
|
|
||||||
margin: 4px 8px;
|
|
||||||
/* Ensure font-size isn't overridden by widget styling (e.g. in forms.css) */
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.button-as-link:hover {
|
|
||||||
background-color: var(--in-content-box-background-hover) !important;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2.lighter-font-weight {
|
|
||||||
font-weight: lighter;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[dir="ltr"] th {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[dir="rtl"] th {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (any-pointer: coarse), (any-pointer: none) {
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
-moz-text-size-adjust: none;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
table,
|
|
||||||
tr,
|
|
||||||
p {
|
|
||||||
display: block;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-top: 2px solid #0a84ff;
|
|
||||||
margin-top: -2px;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr {
|
|
||||||
position: relative;
|
|
||||||
border-bottom: 1px solid #d7d9db;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #000;
|
|
||||||
font-size: 94%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
display: inline-block;
|
|
||||||
text-align: left;
|
|
||||||
padding: 1em;
|
|
||||||
font-weight: bold;
|
|
||||||
border-top-left-radius: 3px;
|
|
||||||
border-top-right-radius: 3px;
|
|
||||||
border: 1px solid #d7d9db;
|
|
||||||
border-bottom: 0;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
background: #f5f5f5;
|
|
||||||
color: #363b40;
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab.active {
|
|
||||||
border-bottom-color: #fff;
|
|
||||||
background: #fff;
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding-bottom: calc(1em + 2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab.active + table {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
td:dir(ltr) {
|
|
||||||
padding-right: 6.5em;
|
|
||||||
}
|
|
||||||
td:dir(rtl) {
|
|
||||||
padding-left: 6.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
td[colspan="4"] {
|
|
||||||
padding: 1em;
|
|
||||||
font-style: italic;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
td:not([colspan]):nth-child(1) {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
td:not([colspan]):nth-child(1) {
|
|
||||||
padding-bottom: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
td:nth-child(3) {
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background: #e8e8e7;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 6em;
|
|
||||||
border: 0;
|
|
||||||
border-left: 1px solid #d7d9db;
|
|
||||||
-moz-appearance: none;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
button:dir(ltr) {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
button:dir(rtl) {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button::-moz-focus-inner {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
<!DOCTYPE HTML>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<base/>
|
|
||||||
|
|
||||||
<!-- If you change this script tag you must update the hash in the extension's
|
|
||||||
`content_security_policy` 'sha256-MmZkN2QaIHhfRWPZ8TVRjijTn5Ci1iEabtTEWrt9CCo=' -->
|
|
||||||
<script>/* globals browser */ document.head.firstElementChild.href = browser.runtime.getURL("");</script>
|
|
||||||
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<link rel="stylesheet" href="about-compat/aboutCompat.css" />
|
|
||||||
<link rel="stylesheet" media="screen and (pointer:fine), projection" type="text/css"
|
|
||||||
href="chrome://global/skin/in-content/common.css"/>
|
|
||||||
<link rel="localization" href="toolkit/about/aboutCompat.ftl"/>
|
|
||||||
<title data-l10n-id="text-title"></title>
|
|
||||||
<script src="about-compat/aboutCompat.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2 class="tab active" data-l10n-id="label-overrides"></h2>
|
|
||||||
<table id="overrides">
|
|
||||||
<col/>
|
|
||||||
<col/>
|
|
||||||
<col/>
|
|
||||||
</table>
|
|
||||||
<h2 class="tab" data-l10n-id="label-interventions"></h2>
|
|
||||||
<table id="interventions">
|
|
||||||
<col/>
|
|
||||||
<col/>
|
|
||||||
<col/>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* globals browser */
|
|
||||||
|
|
||||||
let availablePatches;
|
|
||||||
|
|
||||||
const portToAddon = (function() {
|
|
||||||
let port;
|
|
||||||
|
|
||||||
function connect() {
|
|
||||||
port = browser.runtime.connect({ name: "AboutCompatTab" });
|
|
||||||
port.onMessage.addListener(onMessageFromAddon);
|
|
||||||
port.onDisconnect.addListener(e => {
|
|
||||||
port = undefined;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
connect();
|
|
||||||
|
|
||||||
async function send(message) {
|
|
||||||
if (port) {
|
|
||||||
return port.postMessage(message);
|
|
||||||
}
|
|
||||||
return Promise.reject("background script port disconnected");
|
|
||||||
}
|
|
||||||
|
|
||||||
return { send };
|
|
||||||
})();
|
|
||||||
|
|
||||||
const $ = function(sel) {
|
|
||||||
return document.querySelector(sel);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DOMContentLoadedPromise = new Promise(resolve => {
|
|
||||||
document.addEventListener(
|
|
||||||
"DOMContentLoaded",
|
|
||||||
() => {
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
{ once: true }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
Promise.all([
|
|
||||||
browser.runtime.sendMessage("getOverridesAndInterventions"),
|
|
||||||
DOMContentLoadedPromise,
|
|
||||||
]).then(([info]) => {
|
|
||||||
document.body.addEventListener("click", async evt => {
|
|
||||||
const ele = evt.target;
|
|
||||||
if (ele.nodeName === "BUTTON") {
|
|
||||||
const row = ele.closest("[data-id]");
|
|
||||||
if (row) {
|
|
||||||
evt.preventDefault();
|
|
||||||
ele.disabled = true;
|
|
||||||
const id = row.getAttribute("data-id");
|
|
||||||
try {
|
|
||||||
await browser.runtime.sendMessage({ command: "toggle", id });
|
|
||||||
} catch (_) {
|
|
||||||
ele.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (ele.classList.contains("tab")) {
|
|
||||||
document.querySelectorAll(".tab").forEach(tab => {
|
|
||||||
tab.classList.remove("active");
|
|
||||||
});
|
|
||||||
ele.classList.add("active");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
availablePatches = info;
|
|
||||||
redraw();
|
|
||||||
});
|
|
||||||
|
|
||||||
function onMessageFromAddon(msg) {
|
|
||||||
if ("interventionsChanged" in msg) {
|
|
||||||
redrawTable($("#interventions"), msg.interventionsChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("overridesChanged" in msg) {
|
|
||||||
redrawTable($("#overrides"), msg.overridesChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = msg.toggling || msg.toggled;
|
|
||||||
const button = $(`[data-id="${id}"] button`);
|
|
||||||
if (!button) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const active = msg.active;
|
|
||||||
document.l10n.setAttributes(
|
|
||||||
button,
|
|
||||||
active ? "label-disable" : "label-enable"
|
|
||||||
);
|
|
||||||
button.disabled = !!msg.toggling;
|
|
||||||
}
|
|
||||||
|
|
||||||
function redraw() {
|
|
||||||
if (!availablePatches) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { overrides, interventions } = availablePatches;
|
|
||||||
const showHidden = location.hash === "#all";
|
|
||||||
redrawTable($("#overrides"), overrides, showHidden);
|
|
||||||
redrawTable($("#interventions"), interventions, showHidden);
|
|
||||||
}
|
|
||||||
|
|
||||||
function redrawTable(table, data, showHidden = false) {
|
|
||||||
const df = document.createDocumentFragment();
|
|
||||||
table.querySelectorAll("tr").forEach(tr => {
|
|
||||||
tr.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
let noEntriesMessage;
|
|
||||||
if (data === false) {
|
|
||||||
noEntriesMessage = "text-disabled-in-about-config";
|
|
||||||
} else if (data.length === 0) {
|
|
||||||
noEntriesMessage =
|
|
||||||
table.id === "overrides" ? "text-no-overrides" : "text-no-interventions";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (noEntriesMessage) {
|
|
||||||
const tr = document.createElement("tr");
|
|
||||||
df.appendChild(tr);
|
|
||||||
|
|
||||||
const td = document.createElement("td");
|
|
||||||
td.setAttribute("colspan", "3");
|
|
||||||
document.l10n.setAttributes(td, noEntriesMessage);
|
|
||||||
tr.appendChild(td);
|
|
||||||
|
|
||||||
table.appendChild(df);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const row of data) {
|
|
||||||
if (row.hidden && !showHidden) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tr = document.createElement("tr");
|
|
||||||
tr.setAttribute("data-id", row.id);
|
|
||||||
df.appendChild(tr);
|
|
||||||
|
|
||||||
let td = document.createElement("td");
|
|
||||||
td.innerText = row.domain;
|
|
||||||
tr.appendChild(td);
|
|
||||||
|
|
||||||
td = document.createElement("td");
|
|
||||||
const a = document.createElement("a");
|
|
||||||
const bug = row.bug;
|
|
||||||
a.href = `https://bugzilla.mozilla.org/show_bug.cgi?id=${bug}`;
|
|
||||||
document.l10n.setAttributes(a, "label-more-information", { bug });
|
|
||||||
a.target = "_blank";
|
|
||||||
td.appendChild(a);
|
|
||||||
tr.appendChild(td);
|
|
||||||
|
|
||||||
td = document.createElement("td");
|
|
||||||
tr.appendChild(td);
|
|
||||||
const button = document.createElement("button");
|
|
||||||
document.l10n.setAttributes(
|
|
||||||
button,
|
|
||||||
row.active ? "label-disable" : "label-enable"
|
|
||||||
);
|
|
||||||
td.appendChild(button);
|
|
||||||
}
|
|
||||||
table.appendChild(df);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onhashchange = redraw;
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* global ExtensionAPI, Services, XPCOMUtils */
|
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
|
||||||
this,
|
|
||||||
"Services",
|
|
||||||
"resource://gre/modules/Services.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyServiceGetter(
|
|
||||||
this,
|
|
||||||
"resProto",
|
|
||||||
"@mozilla.org/network/protocol;1?name=resource",
|
|
||||||
"nsISubstitutingProtocolHandler"
|
|
||||||
);
|
|
||||||
|
|
||||||
const ResourceSubstitution = "webcompat";
|
|
||||||
const ProcessScriptURL = "resource://webcompat/aboutPageProcessScript.js";
|
|
||||||
|
|
||||||
this.aboutPage = class extends ExtensionAPI {
|
|
||||||
onStartup() {
|
|
||||||
const { rootURI } = this.extension;
|
|
||||||
|
|
||||||
resProto.setSubstitution(
|
|
||||||
ResourceSubstitution,
|
|
||||||
Services.io.newURI("about-compat/", null, rootURI)
|
|
||||||
);
|
|
||||||
|
|
||||||
Services.ppmm.loadProcessScript(ProcessScriptURL, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
onShutdown() {
|
|
||||||
resProto.setSubstitution(ResourceSubstitution, null);
|
|
||||||
|
|
||||||
Services.ppmm.removeDelayedProcessScript(ProcessScriptURL);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
[{
|
|
||||||
"namespace": "aboutCompat",
|
|
||||||
"description": "Enables the about:compat page"
|
|
||||||
}]
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
/* 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 Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
|
||||||
|
|
||||||
const classID = Components.ID("{97bf9550-2a7b-11e9-b56e-0800200c9a66}");
|
|
||||||
|
|
||||||
if (!Cm.isCIDRegistered(classID)) {
|
|
||||||
const { XPCOMUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/XPCOMUtils.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
const factory = XPCOMUtils.generateSingletonFactory(function() {
|
|
||||||
const { AboutCompat } = ChromeUtils.import(
|
|
||||||
"resource://webcompat/AboutCompat.jsm"
|
|
||||||
);
|
|
||||||
return new AboutCompat();
|
|
||||||
});
|
|
||||||
|
|
||||||
Cm.registerFactory(
|
|
||||||
classID,
|
|
||||||
"about:compat",
|
|
||||||
"@mozilla.org/network/protocol/about;1?what=compat",
|
|
||||||
factory
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,426 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* globals module */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For detailed information on our policies, and a documention on this format
|
|
||||||
* and its possibilites, please check the Mozilla-Wiki at
|
|
||||||
*
|
|
||||||
* https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides
|
|
||||||
*/
|
|
||||||
const AVAILABLE_INJECTIONS = [
|
|
||||||
{
|
|
||||||
id: "testbed-injection",
|
|
||||||
platform: "all",
|
|
||||||
domain: "webcompat-addon-testbed.herokuapp.com",
|
|
||||||
bug: "0000000",
|
|
||||||
hidden: true,
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://webcompat-addon-testbed.herokuapp.com/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file: "injections/css/bug0000000-testbed-css-injection.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
js: [
|
|
||||||
{
|
|
||||||
file: "injections/js/bug0000000-testbed-js-injection.js",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1452707",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "ib.absa.co.za",
|
|
||||||
bug: "1452707",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["https://ib.absa.co.za/*"],
|
|
||||||
js: [
|
|
||||||
{
|
|
||||||
file:
|
|
||||||
"injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1457335",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "histography.io",
|
|
||||||
bug: "1457335",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://histography.io/*"],
|
|
||||||
js: [
|
|
||||||
{
|
|
||||||
file: "injections/js/bug1457335-histography.io-ua-change.js",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1472075",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "bankofamerica.com",
|
|
||||||
bug: "1472075",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://*.bankofamerica.com/*"],
|
|
||||||
js: [
|
|
||||||
{
|
|
||||||
file: "injections/js/bug1472075-bankofamerica.com-ua-change.js",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1472081",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "election.gov.np",
|
|
||||||
bug: "1472081",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["http://202.166.205.141/bbvrs/*"],
|
|
||||||
allFrames: true,
|
|
||||||
js: [
|
|
||||||
{
|
|
||||||
file:
|
|
||||||
"injections/js/bug1472081-election.gov.np-window.sidebar-shim.js",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1482066",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "portalminasnet.com",
|
|
||||||
bug: "1482066",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://portalminasnet.com/*"],
|
|
||||||
allFrames: true,
|
|
||||||
js: [
|
|
||||||
{
|
|
||||||
file:
|
|
||||||
"injections/js/bug1482066-portalminasnet.com-window.sidebar-shim.js",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1570856",
|
|
||||||
platform: "android",
|
|
||||||
domain: "medium.com",
|
|
||||||
bug: "1570856",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://medium.com/*"],
|
|
||||||
js: [
|
|
||||||
{
|
|
||||||
file: "injections/js/bug1570856-medium.com-menu-isTier1.js",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
allFrames: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1579159",
|
|
||||||
platform: "android",
|
|
||||||
domain: "m.tailieu.vn",
|
|
||||||
bug: "1579159",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://m.tailieu.vn/*", "*://m.elib.vn/*"],
|
|
||||||
js: [
|
|
||||||
{
|
|
||||||
file: "injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
allFrames: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1577245",
|
|
||||||
platform: "android",
|
|
||||||
domain: "help.pandora.com",
|
|
||||||
bug: "1577245",
|
|
||||||
contentScripts: {
|
|
||||||
matches: [
|
|
||||||
"https://faq.usps.com/*",
|
|
||||||
"https://help.duo.com/*",
|
|
||||||
"https://help.hulu.com/*",
|
|
||||||
"https://help.pandora.com/*",
|
|
||||||
"https://my211.force.com/*",
|
|
||||||
"https://support.paypay.ne.jp/*",
|
|
||||||
"https://usps.force.com/*",
|
|
||||||
],
|
|
||||||
js: [
|
|
||||||
{
|
|
||||||
file:
|
|
||||||
"injections/js/bug1577245-salesforce-communities-hide-unsupported.js",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1526977",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "sreedharscce.in",
|
|
||||||
bug: "1526977",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://*.sreedharscce.in/authenticate"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file: "injections/css/bug1526977-sreedharscce.in-login-fix.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1518781",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "twitch.tv",
|
|
||||||
bug: "1518781",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://*.twitch.tv/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file: "injections/css/bug1518781-twitch.tv-webkit-scrollbar.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1551672",
|
|
||||||
platform: "android",
|
|
||||||
domain: "Sites using PDK 5 video",
|
|
||||||
bug: "1551672",
|
|
||||||
data: {
|
|
||||||
urls: ["https://*/*/tpPdk.js", "https://*/*/pdk/js/*/*.js"],
|
|
||||||
types: ["script"],
|
|
||||||
},
|
|
||||||
customFunc: "pdk5fix",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1577870",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "Download prompt for files with no content-type",
|
|
||||||
bug: "1577870",
|
|
||||||
data: {
|
|
||||||
urls: [
|
|
||||||
"https://*.linkedin.com/tscp-serving/dtag*",
|
|
||||||
"https://ads-us.rd.linksynergy.com/as.php*",
|
|
||||||
"https://www.office.com/logout?sid*",
|
|
||||||
],
|
|
||||||
contentType: {
|
|
||||||
name: "content-type",
|
|
||||||
value: "text/html; charset=utf-8",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
customFunc: "noSniffFix",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1305028",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "gaming.youtube.com",
|
|
||||||
bug: "1305028",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://gaming.youtube.com/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file:
|
|
||||||
"injections/css/bug1305028-gaming.youtube.com-webkit-scrollbar.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1432935-discord",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "discordapp.com",
|
|
||||||
bug: "1432935",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://discordapp.com/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file:
|
|
||||||
"injections/css/bug1432935-discordapp.com-webkit-scorllbar-white-line.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1561371",
|
|
||||||
platform: "android",
|
|
||||||
domain: "mail.google.com",
|
|
||||||
bug: "1561371",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://mail.google.com/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file:
|
|
||||||
"injections/css/bug1561371-mail.google.com-allow-horizontal-scrolling.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1567610",
|
|
||||||
platform: "all",
|
|
||||||
domain: "dns.google.com",
|
|
||||||
bug: "1567610",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://dns.google.com/*", "*://dns.google/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file: "injections/css/bug1567610-dns.google.com-moz-fit-content.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1568256",
|
|
||||||
platform: "android",
|
|
||||||
domain: "zertifikate.commerzbank.de",
|
|
||||||
bug: "1568256",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://*.zertifikate.commerzbank.de/webforms/mobile/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file: "injections/css/bug1568256-zertifikate.commerzbank.de-flex.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1568908",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "console.cloud.google.com",
|
|
||||||
bug: "1568908",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://*.console.cloud.google.com/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file:
|
|
||||||
"injections/css/bug1568908-console.cloud.google.com-scrollbar-fix.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1570119",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "teamcoco.com",
|
|
||||||
bug: "1570119",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://teamcoco.com/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file: "injections/css/bug1570119-teamcoco.com-scrollbar-width.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1570328",
|
|
||||||
platform: "android",
|
|
||||||
domain: "developer.apple.com",
|
|
||||||
bug: "1570328",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://developer.apple.com/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file:
|
|
||||||
"injections/css/bug1570328-developer-apple.com-transform-scale.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1574973",
|
|
||||||
platform: "android",
|
|
||||||
domain: "patch.com",
|
|
||||||
bug: "1574973",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://patch.com/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file: "injections/css/bug1574973-patch.com-dropdown-menu-fix.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1575000",
|
|
||||||
platform: "all",
|
|
||||||
domain: "apply.lloydsbank.co.uk",
|
|
||||||
bug: "1575000",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://apply.lloydsbank.co.uk/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file:
|
|
||||||
"injections/css/bug1575000-apply.lloydsbank.co.uk-radio-buttons-fix.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1575011",
|
|
||||||
platform: "android",
|
|
||||||
domain: "holiday-weather.com",
|
|
||||||
bug: "1575011",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://*.holiday-weather.com/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file:
|
|
||||||
"injections/css/bug1575011-holiday-weather.com-scrolling-fix.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1575017",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "dunkindonuts.com",
|
|
||||||
bug: "1575017",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://*.dunkindonuts.com/en/sign-in*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file: "injections/css/bug1575017-dunkindonuts.com-flex-basis.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1577270",
|
|
||||||
platform: "android",
|
|
||||||
domain: "binance.com",
|
|
||||||
bug: "1577270",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://*.binance.com/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file: "injections/css/bug1577270-binance.com-calc-height-fix.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bug1577297",
|
|
||||||
platform: "android",
|
|
||||||
domain: "kitkat.com.au",
|
|
||||||
bug: "1577297",
|
|
||||||
contentScripts: {
|
|
||||||
matches: ["*://*.kitkat.com.au/*"],
|
|
||||||
css: [
|
|
||||||
{
|
|
||||||
file: "injections/css/bug1577297-kitkat.com.au-slider-width-fix.css",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
module.exports = AVAILABLE_INJECTIONS;
|
|
||||||
|
|
@ -1,580 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* globals module, require */
|
|
||||||
|
|
||||||
// This is a hack for the tests.
|
|
||||||
if (typeof getMatchPatternsForGoogleURL === "undefined") {
|
|
||||||
var getMatchPatternsForGoogleURL = require("../lib/google");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For detailed information on our policies, and a documention on this format
|
|
||||||
* and its possibilites, please check the Mozilla-Wiki at
|
|
||||||
*
|
|
||||||
* https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides
|
|
||||||
*/
|
|
||||||
const AVAILABLE_UA_OVERRIDES = [
|
|
||||||
{
|
|
||||||
id: "testbed-override",
|
|
||||||
platform: "all",
|
|
||||||
domain: "webcompat-addon-testbed.herokuapp.com",
|
|
||||||
bug: "0000000",
|
|
||||||
config: {
|
|
||||||
hidden: true,
|
|
||||||
matches: ["*://webcompat-addon-testbed.herokuapp.com/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return (
|
|
||||||
UAHelpers.getPrefix(originalUA) +
|
|
||||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36 for WebCompat"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1564594 - Create UA override for Enhanced Search on Firefox Android
|
|
||||||
*
|
|
||||||
* Enables the Chrome Google Search experience for Fennec users.
|
|
||||||
*/
|
|
||||||
id: "bug1564594",
|
|
||||||
platform: "android",
|
|
||||||
domain: "Enhanced Search",
|
|
||||||
bug: "1567945",
|
|
||||||
config: {
|
|
||||||
matches: [
|
|
||||||
...getMatchPatternsForGoogleURL("images.google"),
|
|
||||||
...getMatchPatternsForGoogleURL("maps.google"),
|
|
||||||
...getMatchPatternsForGoogleURL("news.google"),
|
|
||||||
...getMatchPatternsForGoogleURL("www.google"),
|
|
||||||
],
|
|
||||||
blocks: [...getMatchPatternsForGoogleURL("www.google", "serviceworker")],
|
|
||||||
permanentPref: "enable_enhanced_search",
|
|
||||||
telemetryKey: "enhancedSearch",
|
|
||||||
experiment: ["enhanced-search", "enhanced-search-control"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return UAHelpers.getDeviceAppropriateChromeUA();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1563839 - rolb.santanderbank.com - Build UA override
|
|
||||||
* WebCompat issue #33462 - https://webcompat.com/issues/33462
|
|
||||||
*
|
|
||||||
* santanderbank expects UA to have 'like Gecko', otherwise it runs
|
|
||||||
* xmlDoc.onload whose support has been dropped. It results in missing labels in forms
|
|
||||||
* and some other issues. Adding 'like Gecko' fixes those issues.
|
|
||||||
*/
|
|
||||||
id: "bug1563839",
|
|
||||||
platform: "all",
|
|
||||||
domain: "rolb.santanderbank.com",
|
|
||||||
bug: "1563839",
|
|
||||||
config: {
|
|
||||||
matches: [
|
|
||||||
"*://*.santander.co.uk/*",
|
|
||||||
"*://bob.santanderbank.com/*",
|
|
||||||
"*://rolb.santanderbank.com/*",
|
|
||||||
],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return originalUA.replace("Gecko", "like Gecko");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1577179 - UA override for supportforms.embarcadero.com
|
|
||||||
* WebCompat issue #34682 - https://webcompat.com/issues/34682
|
|
||||||
*
|
|
||||||
* supportforms.embarcadero.com has a constant onchange event on a product selector
|
|
||||||
* which makes it unusable. Spoofing as Chrome allows to stop event from firing
|
|
||||||
*/
|
|
||||||
id: "bug1577179",
|
|
||||||
platform: "all",
|
|
||||||
domain: "supportforms.embarcadero.com",
|
|
||||||
bug: "1577179",
|
|
||||||
config: {
|
|
||||||
matches: ["*://supportforms.embarcadero.com/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return (
|
|
||||||
UAHelpers.getPrefix(originalUA) +
|
|
||||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1577519 - att.tv - Create a UA override for att.tv for playback on desktop
|
|
||||||
* WebCompat issue #3846 - https://webcompat.com/issues/3846
|
|
||||||
*
|
|
||||||
* att.tv (atttvnow.com) is blocking Firefox via UA sniffing. Spoofing as Chrome allows
|
|
||||||
* to access the site and playback works fine. This is former directvnow.com
|
|
||||||
*/
|
|
||||||
id: "bug1577519",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "att.tv",
|
|
||||||
bug: "1577519",
|
|
||||||
config: {
|
|
||||||
matches: ["*://*.att.tv/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return (
|
|
||||||
UAHelpers.getPrefix(originalUA) +
|
|
||||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1570108 - steamcommunity.com - UA override for steamcommunity.com
|
|
||||||
* WebCompat issue #34171 - https://webcompat.com/issues/34171
|
|
||||||
*
|
|
||||||
* steamcommunity.com blocks chat feature for Firefox users showing unsupported browser message.
|
|
||||||
* When spoofing as Chrome the chat works fine
|
|
||||||
*/
|
|
||||||
id: "bug1570108",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "steamcommunity.com",
|
|
||||||
bug: "1570108",
|
|
||||||
config: {
|
|
||||||
matches: ["*://steamcommunity.com/chat*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return (
|
|
||||||
UAHelpers.getPrefix(originalUA) +
|
|
||||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1582582 - sling.com - UA override for sling.com
|
|
||||||
* WebCompat issue #17804 - https://webcompat.com/issues/17804
|
|
||||||
*
|
|
||||||
* sling.com blocks Firefox users showing unsupported browser message.
|
|
||||||
* When spoofing as Chrome playing content works fine
|
|
||||||
*/
|
|
||||||
id: "bug1582582",
|
|
||||||
platform: "desktop",
|
|
||||||
domain: "sling.com",
|
|
||||||
bug: "1582582",
|
|
||||||
config: {
|
|
||||||
matches: ["https://watch.sling.com/*", "https://www.sling.com/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return (
|
|
||||||
UAHelpers.getPrefix(originalUA) +
|
|
||||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1480710 - m.imgur.com - Build UA override
|
|
||||||
* WebCompat issue #13154 - https://webcompat.com/issues/13154
|
|
||||||
*
|
|
||||||
* imgur returns a 404 for requests to CSS and JS file if requested with a Fennec
|
|
||||||
* User Agent. By removing the Fennec identifies and adding Chrome Mobile's, we
|
|
||||||
* receive the correct CSS and JS files.
|
|
||||||
*/
|
|
||||||
id: "bug1480710",
|
|
||||||
platform: "android",
|
|
||||||
domain: "m.imgur.com",
|
|
||||||
bug: "1480710",
|
|
||||||
config: {
|
|
||||||
matches: ["*://m.imgur.com/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return (
|
|
||||||
UAHelpers.getPrefix(originalUA) +
|
|
||||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 945963 - tieba.baidu.com serves simplified mobile content to Firefox Android
|
|
||||||
* WebCompat issue #18455 - https://webcompat.com/issues/18455
|
|
||||||
*
|
|
||||||
* tieba.baidu.com and tiebac.baidu.com serve a heavily simplified and less functional
|
|
||||||
* mobile experience to Firefox for Android users. Adding the AppleWebKit indicator
|
|
||||||
* to the User Agent gets us the same experience.
|
|
||||||
*/
|
|
||||||
id: "bug945963",
|
|
||||||
platform: "android",
|
|
||||||
domain: "tieba.baidu.com",
|
|
||||||
bug: "945963",
|
|
||||||
config: {
|
|
||||||
matches: [
|
|
||||||
"*://tieba.baidu.com/*",
|
|
||||||
"*://tiebac.baidu.com/*",
|
|
||||||
"*://zhidao.baidu.com/*",
|
|
||||||
],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return originalUA + " AppleWebKit/537.36 (KHTML, like Gecko)";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1177298 - Write UA overrides for top Japanese Sites
|
|
||||||
* (Imported from ua-update.json.in)
|
|
||||||
*
|
|
||||||
* To receive the proper mobile version instead of the desktop version or
|
|
||||||
* a lower grade mobile experience, the UA is spoofed.
|
|
||||||
*/
|
|
||||||
id: "bug1177298-2",
|
|
||||||
platform: "android",
|
|
||||||
domain: "lohaco.jp",
|
|
||||||
bug: "1177298",
|
|
||||||
config: {
|
|
||||||
matches: ["*://*.lohaco.jp/*"],
|
|
||||||
uaTransformer: _ => {
|
|
||||||
return "Mozilla/5.0 (Linux; Android 5.0.2; Galaxy Nexus Build/IMM76B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1177298 - Write UA overrides for top Japanese Sites
|
|
||||||
* (Imported from ua-update.json.in)
|
|
||||||
*
|
|
||||||
* To receive the proper mobile version instead of the desktop version or
|
|
||||||
* a lower grade mobile experience, the UA is spoofed.
|
|
||||||
*/
|
|
||||||
id: "bug1177298-3",
|
|
||||||
platform: "android",
|
|
||||||
domain: "nhk.or.jp",
|
|
||||||
bug: "1177298",
|
|
||||||
config: {
|
|
||||||
matches: ["*://*.nhk.or.jp/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return originalUA + " AppleWebKit";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1338260 - Add UA override for directTV
|
|
||||||
* (Imported from ua-update.json.in)
|
|
||||||
*
|
|
||||||
* DirectTV has issues with scrolling and cut-off images. Pretending to be
|
|
||||||
* Chrome for Android fixes those issues.
|
|
||||||
*/
|
|
||||||
id: "bug1338260",
|
|
||||||
platform: "android",
|
|
||||||
domain: "directv.com",
|
|
||||||
bug: "1338260",
|
|
||||||
config: {
|
|
||||||
matches: ["*://*.directv.com/*"],
|
|
||||||
uaTransformer: _ => {
|
|
||||||
return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1385206 - Create UA override for rakuten.co.jp on Firefox Android
|
|
||||||
* (Imported from ua-update.json.in)
|
|
||||||
*
|
|
||||||
* rakuten.co.jp serves a Desktop version if Firefox is included in the UA.
|
|
||||||
*/
|
|
||||||
id: "bug1385206",
|
|
||||||
platform: "android",
|
|
||||||
domain: "rakuten.co.jp",
|
|
||||||
bug: "1385206",
|
|
||||||
config: {
|
|
||||||
matches: ["*://*.rakuten.co.jp/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return originalUA.replace(/Firefox.+$/, "");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 969844 - mobile.de sends desktop site to Firefox on Android
|
|
||||||
*
|
|
||||||
* mobile.de sends the desktop site to Fennec. Spooing as Chrome works fine.
|
|
||||||
*/
|
|
||||||
id: "bug969844",
|
|
||||||
platform: "android",
|
|
||||||
domain: "mobile.de",
|
|
||||||
bug: "969844",
|
|
||||||
config: {
|
|
||||||
matches: ["*://*.mobile.de/*"],
|
|
||||||
uaTransformer: _ => {
|
|
||||||
return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1509831 - cc.com - Add UA override for CC.com
|
|
||||||
* WebCompat issue #329 - https://webcompat.com/issues/329
|
|
||||||
*
|
|
||||||
* ComedyCentral blocks Firefox for not being able to play HLS, which was
|
|
||||||
* true in previous versions, but no longer is. With a spoofed Chrome UA,
|
|
||||||
* the site works just fine.
|
|
||||||
*/
|
|
||||||
id: "bug1509831",
|
|
||||||
platform: "android",
|
|
||||||
domain: "cc.com",
|
|
||||||
bug: "1509831",
|
|
||||||
config: {
|
|
||||||
matches: ["*://*.cc.com/*"],
|
|
||||||
uaTransformer: _ => {
|
|
||||||
return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G920F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1508516 - cineflix.com.br - Add UA override for cineflix.com.br/m/
|
|
||||||
* WebCompat issue #21553 - https://webcompat.com/issues/21553
|
|
||||||
*
|
|
||||||
* The site renders a blank page with any Firefox snipped in the UA as it
|
|
||||||
* is running into an exception. Spoofing as Chrome makes the site work
|
|
||||||
* fine.
|
|
||||||
*/
|
|
||||||
id: "bug1508516",
|
|
||||||
platform: "android",
|
|
||||||
domain: "cineflix.com.br",
|
|
||||||
bug: "1508516",
|
|
||||||
config: {
|
|
||||||
matches: ["*://*.cineflix.com.br/m/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return (
|
|
||||||
UAHelpers.getPrefix(originalUA) +
|
|
||||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1509852 - redbull.com - Add UA override for redbull.com
|
|
||||||
* WebCompat issue #21439 - https://webcompat.com/issues/21439
|
|
||||||
*
|
|
||||||
* Redbull.com blocks some features, for example the live video player, for
|
|
||||||
* Fennec. Spoofing as Chrome results in us rendering the video just fine,
|
|
||||||
* and everything else works as well.
|
|
||||||
*/
|
|
||||||
id: "bug1509852",
|
|
||||||
platform: "android",
|
|
||||||
domain: "redbull.com",
|
|
||||||
bug: "1509852",
|
|
||||||
config: {
|
|
||||||
matches: ["*://*.redbull.com/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return (
|
|
||||||
UAHelpers.getPrefix(originalUA) +
|
|
||||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1509873 - zmags.com - Add UA override for secure.viewer.zmags.com
|
|
||||||
* WebCompat issue #21576 - https://webcompat.com/issues/21576
|
|
||||||
*
|
|
||||||
* The zmags viewer locks out Fennec with a "Browser unsupported" message,
|
|
||||||
* but tests showed that it works just fine with a Chrome UA. Outreach
|
|
||||||
* attempts were unsuccessful, and as the site has a relatively high rank,
|
|
||||||
* we alter the UA.
|
|
||||||
*/
|
|
||||||
id: "bug1509873",
|
|
||||||
platform: "android",
|
|
||||||
domain: "zmags.com",
|
|
||||||
bug: "1509873",
|
|
||||||
config: {
|
|
||||||
matches: ["*://*.viewer.zmags.com/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return (
|
|
||||||
UAHelpers.getPrefix(originalUA) +
|
|
||||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1566253 - posts.google.com - Add UA override for posts.google.com
|
|
||||||
* WebCompat issue #17870 - https://webcompat.com/issues/17870
|
|
||||||
*
|
|
||||||
* posts.google.com displaying "Your browser doesn't support this page".
|
|
||||||
* Spoofing as Chrome works fine.
|
|
||||||
*/
|
|
||||||
id: "bug1566253",
|
|
||||||
platform: "android",
|
|
||||||
domain: "posts.google.com",
|
|
||||||
bug: "1566253",
|
|
||||||
config: {
|
|
||||||
matches: ["*://posts.google.com/*"],
|
|
||||||
uaTransformer: _ => {
|
|
||||||
return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G900M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.101 Mobile Safari/537.36";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1567945 - Create UA override for beeg.com on Firefox Android
|
|
||||||
* WebCompat issue #16648 - https://webcompat.com/issues/16648
|
|
||||||
*
|
|
||||||
* beeg.com is hiding content of a page with video if Firefox exists in UA,
|
|
||||||
* replacing "Firefox" with an empty string makes the page load
|
|
||||||
*/
|
|
||||||
id: "bug1567945",
|
|
||||||
platform: "android",
|
|
||||||
domain: "beeg.com",
|
|
||||||
bug: "1567945",
|
|
||||||
config: {
|
|
||||||
matches: ["*://beeg.com/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return originalUA.replace(/Firefox.+$/, "");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1574522 - UA override for enuri.com on Firefox for Android
|
|
||||||
* WebCompat issue #37139 - https://webcompat.com/issues/37139
|
|
||||||
*
|
|
||||||
* enuri.com returns a different template for Firefox on Android
|
|
||||||
* based on server side UA detection. This results in page content cut offs.
|
|
||||||
* Spoofing as Chrome fixes the issue
|
|
||||||
*/
|
|
||||||
id: "bug1574522",
|
|
||||||
platform: "android",
|
|
||||||
domain: "enuri.com",
|
|
||||||
bug: "1574522",
|
|
||||||
config: {
|
|
||||||
matches: ["*://enuri.com/*"],
|
|
||||||
uaTransformer: _ => {
|
|
||||||
return "Mozilla/5.0 (Linux; Android 6.0.1; SM-G900M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1574564 - UA override for ceskatelevize.cz on Firefox for Android
|
|
||||||
* WebCompat issue #15467 - https://webcompat.com/issues/15467
|
|
||||||
*
|
|
||||||
* ceskatelevize sets streamingProtocol depending on the User-Agent it sees
|
|
||||||
* in the request headers, returning DASH for Chrome, HLS for iOS,
|
|
||||||
* and Flash for Fennec. Since Fennec has no Flash, the video doesn't work.
|
|
||||||
* Spoofing as Chrome makes the video play
|
|
||||||
*/
|
|
||||||
id: "bug1574564",
|
|
||||||
platform: "android",
|
|
||||||
domain: "ceskatelevize.cz",
|
|
||||||
bug: "1574564",
|
|
||||||
config: {
|
|
||||||
matches: ["*://*.ceskatelevize.cz/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return (
|
|
||||||
UAHelpers.getPrefix(originalUA) +
|
|
||||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1577240 - UA override for heb.com on Firefox for Android
|
|
||||||
* WebCompat issue #33613 - https://webcompat.com/issues/33613
|
|
||||||
*
|
|
||||||
* heb.com shows desktop site on Firefox for Android for some pages based on
|
|
||||||
* UA detection. Spoofing as Chrome allows to get mobile site.
|
|
||||||
*/
|
|
||||||
id: "bug1577240",
|
|
||||||
platform: "android",
|
|
||||||
domain: "heb.com",
|
|
||||||
bug: "1577240",
|
|
||||||
config: {
|
|
||||||
matches: ["*://*.heb.com/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return (
|
|
||||||
UAHelpers.getPrefix(originalUA) +
|
|
||||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1577250 - UA override for homebook.pl on Firefox for Android
|
|
||||||
* WebCompat issue #24044 - https://webcompat.com/issues/24044
|
|
||||||
*
|
|
||||||
* homebook.pl shows desktop site on Firefox for Android based on
|
|
||||||
* UA detection. Spoofing as Chrome allows to get mobile site.
|
|
||||||
*/
|
|
||||||
id: "bug1577250",
|
|
||||||
platform: "android",
|
|
||||||
domain: "homebook.pl",
|
|
||||||
bug: "1577250",
|
|
||||||
config: {
|
|
||||||
matches: ["*://*.homebook.pl/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return (
|
|
||||||
UAHelpers.getPrefix(originalUA) +
|
|
||||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Bug 1577267 - UA override for metfone.com.kh on Firefox for Android
|
|
||||||
* WebCompat issue #16363 - https://webcompat.com/issues/16363
|
|
||||||
*
|
|
||||||
* metfone.com.kh has a server side UA detection which returns desktop site
|
|
||||||
* for Firefox for Android. Spoofing as Chrome allows to receive mobile version
|
|
||||||
*/
|
|
||||||
id: "bug1577267",
|
|
||||||
platform: "android",
|
|
||||||
domain: "metfone.com.kh",
|
|
||||||
bug: "1577267",
|
|
||||||
config: {
|
|
||||||
matches: ["*://*.metfone.com.kh/*"],
|
|
||||||
uaTransformer: originalUA => {
|
|
||||||
return (
|
|
||||||
UAHelpers.getPrefix(originalUA) +
|
|
||||||
" AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const UAHelpers = {
|
|
||||||
getDeviceAppropriateChromeUA() {
|
|
||||||
if (!UAHelpers._deviceAppropriateChromeUA) {
|
|
||||||
const userAgent =
|
|
||||||
typeof navigator !== "undefined" ? navigator.userAgent : "";
|
|
||||||
const RunningFirefoxVersion = (userAgent.match(/Firefox\/([0-9.]+)/) || [
|
|
||||||
"",
|
|
||||||
"58.0",
|
|
||||||
])[1];
|
|
||||||
const RunningAndroidVersion =
|
|
||||||
userAgent.match(/Android\/[0-9.]+/) || "Android 6.0";
|
|
||||||
const ChromeVersionToMimic = "76.0.3809.111";
|
|
||||||
const ChromePhoneUA = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; Nexus 5 Build/MRA58N) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${ChromeVersionToMimic} Mobile Safari/537.36`;
|
|
||||||
const ChromeTabletUA = `Mozilla/5.0 (Linux; ${RunningAndroidVersion}; Nexus 7 Build/JSS15Q) FxQuantum/${RunningFirefoxVersion} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${ChromeVersionToMimic} Safari/537.36`;
|
|
||||||
const IsPhone = userAgent.includes("Mobile");
|
|
||||||
UAHelpers._deviceAppropriateChromeUA = IsPhone
|
|
||||||
? ChromePhoneUA
|
|
||||||
: ChromeTabletUA;
|
|
||||||
}
|
|
||||||
return UAHelpers._deviceAppropriateChromeUA;
|
|
||||||
},
|
|
||||||
getPrefix(originalUA) {
|
|
||||||
return originalUA.substr(0, originalUA.indexOf(")") + 1);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = AVAILABLE_UA_OVERRIDES;
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* global ExtensionAPI, ExtensionCommon, Services, XPCOMUtils */
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
|
||||||
Services: "resource://gre/modules/Services.jsm",
|
|
||||||
});
|
|
||||||
|
|
||||||
this.aboutConfigPrefs = class extends ExtensionAPI {
|
|
||||||
getAPI(context) {
|
|
||||||
const EventManager = ExtensionCommon.EventManager;
|
|
||||||
const extensionIDBase = context.extension.id.split("@")[0];
|
|
||||||
const extensionPrefNameBase = `extensions.${extensionIDBase}.`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
aboutConfigPrefs: {
|
|
||||||
onPrefChange: new EventManager({
|
|
||||||
context,
|
|
||||||
name: "aboutConfigPrefs.onUAOverridesPrefChange",
|
|
||||||
register: (fire, name) => {
|
|
||||||
const prefName = `${extensionPrefNameBase}${name}`;
|
|
||||||
const callback = () => {
|
|
||||||
fire.async(name).catch(() => {}); // ignore Message Manager disconnects
|
|
||||||
};
|
|
||||||
Services.prefs.addObserver(prefName, callback);
|
|
||||||
return () => {
|
|
||||||
Services.prefs.removeObserver(prefName, callback);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}).api(),
|
|
||||||
async getPref(name) {
|
|
||||||
try {
|
|
||||||
return Services.prefs.getBoolPref(
|
|
||||||
`${extensionPrefNameBase}${name}`
|
|
||||||
);
|
|
||||||
} catch (_) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async setPref(name, value) {
|
|
||||||
Services.prefs.setBoolPref(`${extensionPrefNameBase}${name}`, value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"namespace": "aboutConfigPrefs",
|
|
||||||
"description": "experimental API extension to allow access to about:config preferences",
|
|
||||||
"events": [
|
|
||||||
{
|
|
||||||
"name": "onPrefChange",
|
|
||||||
"type": "function",
|
|
||||||
"parameters": [{
|
|
||||||
"name": "name",
|
|
||||||
"type": "string",
|
|
||||||
"description": "The preference which changed"
|
|
||||||
}],
|
|
||||||
"extraParameters": [{
|
|
||||||
"name": "name",
|
|
||||||
"type": "string",
|
|
||||||
"description": "The preference to monitor"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"functions": [
|
|
||||||
{
|
|
||||||
"name": "getPref",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Get a preference's value",
|
|
||||||
"parameters": [{
|
|
||||||
"name": "name",
|
|
||||||
"type": "string",
|
|
||||||
"description": "The preference name"
|
|
||||||
}],
|
|
||||||
"async": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "setPref",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Set a preference's value",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"type": "string",
|
|
||||||
"description": "The preference name"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "value",
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "The new value"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"async": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* global ExtensionAPI, Services, XPCOMUtils */
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
|
||||||
EventDispatcher: "resource://gre/modules/Messaging.jsm",
|
|
||||||
Services: "resource://gre/modules/Services.jsm",
|
|
||||||
});
|
|
||||||
|
|
||||||
this.experiments = class extends ExtensionAPI {
|
|
||||||
getAPI(context) {
|
|
||||||
function promiseActiveExperiments() {
|
|
||||||
return EventDispatcher.instance.sendRequestForResult({
|
|
||||||
type: "Experiments:GetActive",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
experiments: {
|
|
||||||
async isActive(name) {
|
|
||||||
if (!Services.androidBridge || !Services.androidBridge.isFennec) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return promiseActiveExperiments().then(experiments => {
|
|
||||||
return experiments.includes(name);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"namespace": "experiments",
|
|
||||||
"description": "experimental API extension to allow checking the status of Fennec experiments via Switchboard",
|
|
||||||
"functions": [
|
|
||||||
{
|
|
||||||
"name": "isActive",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Determine if a given experiment is active.",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"type": "string",
|
|
||||||
"description": "The experiment's name"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"async": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
/* global ExtensionAPI, Services, XPCOMUtils */
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
|
||||||
Services: "resource://gre/modules/Services.jsm",
|
|
||||||
SharedPreferences: "resource://gre/modules/SharedPreferences.jsm",
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sharedPreferences = class extends ExtensionAPI {
|
|
||||||
getAPI(context) {
|
|
||||||
return {
|
|
||||||
sharedPreferences: {
|
|
||||||
async setCharPref(name, value) {
|
|
||||||
if (!Services.androidBridge || !Services.androidBridge.isFennec) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SharedPreferences.forApp().setCharPref(name, value);
|
|
||||||
},
|
|
||||||
async setBoolPref(name, value) {
|
|
||||||
if (!Services.androidBridge || !Services.androidBridge.isFennec) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SharedPreferences.forApp().setBoolPref(name, value);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"namespace": "sharedPreferences",
|
|
||||||
"description": "experimental API extension to allow setting SharedPreferences on Fennec",
|
|
||||||
"functions": [
|
|
||||||
{
|
|
||||||
"name": "setBoolPref",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Set the value of a boolean Fennec SharedPreference",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"type": "string",
|
|
||||||
"description": "The key name"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "value",
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "The new value"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"async": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "setCharPref",
|
|
||||||
"type": "function",
|
|
||||||
"description": "Set the value of a string Fennec SharedPreference",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"type": "string",
|
|
||||||
"description": "The key name"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "value",
|
|
||||||
"type": "string",
|
|
||||||
"description": "The new value"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"async": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
#css-injection.red {
|
|
||||||
background-color: #0f0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
/**
|
|
||||||
* gaming.youtube.com - The vertical scrollbar displayed for the main pane is
|
|
||||||
* partially overlapped by the video itself
|
|
||||||
* Bug #1305028 - https://bugzilla.mozilla.org/show_bug.cgi?id=1305028
|
|
||||||
*
|
|
||||||
* The scrollbar in the main player area is overlapped by the player, making the
|
|
||||||
* design look broken. In Chrome, YouTube is using ::-webkit-scrollbar to style
|
|
||||||
* the bar to match their expectations, but this doesn't work in Firefox.
|
|
||||||
* To make it look less broken, we hide the scrollbar for the main video pane
|
|
||||||
* entirely.
|
|
||||||
*/
|
|
||||||
ytg-scroll-pane.ytg-watch-page {
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
/**
|
|
||||||
* discordapp.com - -webkit-scrollbar dependency causes visible white line
|
|
||||||
* Part of Bug #1432935 - https://bugzilla.mozilla.org/show_bug.cgi?id=1432935
|
|
||||||
* WebCompat issue #7919 - https://webcompat.com/issues/7919
|
|
||||||
*
|
|
||||||
* Discord depends on -webkit-scrollbar for styling and hiding their scrollbars
|
|
||||||
* in the UI. Previously, the scrollbars overlapped content permanently.
|
|
||||||
* However, this issue seems to be addressed now, but a small white line
|
|
||||||
* still remains. While Discord is working on this, let's get rid of these
|
|
||||||
* lines.
|
|
||||||
*/
|
|
||||||
.themeGhostHairline-DBD-2d .pad-29zQak,
|
|
||||||
.themeGhostHairlineChannels-3G0x9_ .pad-29zQak {
|
|
||||||
width: 3px !important;
|
|
||||||
left: -3px !important;
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
/**
|
|
||||||
* twitch.tv - Comment interaction button is overlayed by scrollbar
|
|
||||||
* Bug #1518781 - https://bugzilla.mozilla.org/show_bug.cgi?id=1518781
|
|
||||||
*
|
|
||||||
* The interaction buttons in Twitch' chat are partly overlayed by the
|
|
||||||
* scrollbar, which makes them hard to use. Twitch uses
|
|
||||||
* ::-webkit-scrollbar to make the scrollbar thinner, which isn't working in
|
|
||||||
* Firefox.
|
|
||||||
* Given that even scrollbar-width: thin; is not enough (see Bugzilla), let's
|
|
||||||
* remove it entirely.
|
|
||||||
*/
|
|
||||||
.video-chat__message-list-wrapper {
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
/**
|
|
||||||
* sreedharscce.in - Fix login form with CSS intervention
|
|
||||||
* Bug #1526977 - https://bugzilla.mozilla.org/show_bug.cgi?id=1526977
|
|
||||||
* WebCompat issue #21505 - https://webcompat.com/issues/21505
|
|
||||||
*
|
|
||||||
* The login form is partly moved out of the screen on sreedharscce.in in
|
|
||||||
* Firefox. Enforcing the body height to the full viewport fixes this issue,
|
|
||||||
* as the login form itself is posititoned with `position: absolute;`.
|
|
||||||
*/
|
|
||||||
body {
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
/**
|
|
||||||
* mail.google.com - The HTML email view does not allow horizontal scrolling
|
|
||||||
* on Fennec due to a missing CSS rule which is only served to Chrome.
|
|
||||||
* Bug #1561371 - https://bugzilla.mozilla.org/show_bug.cgi?id=1561371
|
|
||||||
*
|
|
||||||
* HTML emails may sometimes contain content that does not wrap, yet the
|
|
||||||
* CSS served to Fennec does not permit scrolling horizontally. To prevent
|
|
||||||
* this UX frustration, we enable horizontal scrolling.
|
|
||||||
*/
|
|
||||||
body > #views {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
/**
|
|
||||||
* dns.google.com - Page content is shifted to the left side of the page
|
|
||||||
* Bug #1567610 - https://bugzilla.mozilla.org/show_bug.cgi?id=1567610
|
|
||||||
* WebCompat issue #22494 - https://webcompat.com/issues/22494
|
|
||||||
*
|
|
||||||
* Affected element is styled with width:fit-content; which is not
|
|
||||||
* supported by Firefox yet, see https://bugzilla.mozilla.org/show_bug.cgi?id=1495868
|
|
||||||
* Adding -moz-fit-content fixes the issue
|
|
||||||
*/
|
|
||||||
main > .ng-star-inserted > .centered {
|
|
||||||
width: -moz-fit-content;
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
/**
|
|
||||||
* zertifikate.commerzbank.de - clickable elements on the page are collapsed
|
|
||||||
* Bug #1568256 - https://bugzilla.mozilla.org/show_bug.cgi?id=1568256
|
|
||||||
* WebCompat issue #9102 - https://webcompat.com/issues/9102
|
|
||||||
*
|
|
||||||
* Affected elements have display:-webkit-box and display:flex applied, however,
|
|
||||||
* listed in wrong order, so display:-webkit-box is becoming the final say.
|
|
||||||
* Adding display: flex for those elements fixes the issue
|
|
||||||
*/
|
|
||||||
.x-layout-box {
|
|
||||||
display: flex !important;
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
/**
|
|
||||||
* console.cloud.google.com - Double scrollbar visisible on long pages
|
|
||||||
* Bug #1568908 - https://bugzilla.mozilla.org/show_bug.cgi?id=1568908
|
|
||||||
* WebCompat issue #33164 - https://webcompat.com/issues/33164
|
|
||||||
*
|
|
||||||
* For pages that have contents heigher than the page's height, a secondary
|
|
||||||
* scrollbar outside the scrollable content area is visible. This is caused
|
|
||||||
* by a difference in Flexbox behavior, which is being addressed in
|
|
||||||
* https://bugs.chromium.org/p/chromium/issues/detail?id=981134
|
|
||||||
*
|
|
||||||
* Until this fix hits release and Google has updated their UI properly,
|
|
||||||
* this intervention addresses the differences.
|
|
||||||
*/
|
|
||||||
central-page-area {
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
/**
|
|
||||||
* teamcoco.com - a scrollbar at the top covering navigation menu
|
|
||||||
* Bug #1570119 - https://bugzilla.mozilla.org/show_bug.cgi?id=1570119
|
|
||||||
*
|
|
||||||
* The scrollbar is covering navigation items making them unusable.
|
|
||||||
* There are ::-webkit-scrollbar css rules already applied to the scrollbar,
|
|
||||||
* hiding it in Chrome. Adding the scrollbar-width: none fixes the issue in Firefox.
|
|
||||||
*/
|
|
||||||
.css-bdnz85 {
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
/**
|
|
||||||
* developer.apple.com - content of the page is shifted to the left
|
|
||||||
* Bug #1570328 - https://bugzilla.mozilla.org/show_bug.cgi?id=1570328
|
|
||||||
* WebCompat issue #4070 - https://webcompat.com/issues/4070
|
|
||||||
*
|
|
||||||
* The site is relying on zoom property which is not supported by Mozilla,
|
|
||||||
* see https://bugzilla.mozilla.org/show_bug.cgi?id=390936. Adding a combination
|
|
||||||
* of transform: scale(1.4), transform-origin and width fixes the issue
|
|
||||||
*/
|
|
||||||
@media only screen and (min-device-width: 320px) and (max-device-width: 980px),
|
|
||||||
(min-device-width: 1024px) and (max-device-width: 1024px) and (min-device-height: 1366px) and (max-device-height: 1366px) and (min-width: 320px) and (max-width: 980px) {
|
|
||||||
#tocContainer {
|
|
||||||
transform-origin: 0 0;
|
|
||||||
transform: scale(1.4);
|
|
||||||
width: 71.4%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
/**
|
|
||||||
* patch.com - sub-menu expands at the bottom of the page and overlaps other elements.
|
|
||||||
* Bug #1574973 - https://bugzilla.mozilla.org/show_bug.cgi?id=1574973
|
|
||||||
* WebCompat issue #25384 - https://webcompat.com/issues/25384
|
|
||||||
*
|
|
||||||
* patch.con has a top:100% style on the relatively-positioned element
|
|
||||||
* with class="dropdown-menu", and Firefox is incorrectly honoring that
|
|
||||||
* style (resolving it to something nonzero), whereas Chrome just treats it as "auto"
|
|
||||||
* see https://bugzilla.mozilla.org/show_bug.cgi?id=1092007
|
|
||||||
*/
|
|
||||||
#patch-nav-secondary .dropdown-menu {
|
|
||||||
top: auto;
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
/**
|
|
||||||
* apply.lloydsbank.co.uk - radio buttons are misplaced
|
|
||||||
* Bug #1575000 - https://bugzilla.mozilla.org/show_bug.cgi?id=1575000
|
|
||||||
* WebCompat issue #34969 - https://webcompat.com/issues/34969
|
|
||||||
*
|
|
||||||
* Radio buttons are displaced to the left due to positioning issue of ::before
|
|
||||||
* pseudo element, adding position relative to it's parent fixes the issue.
|
|
||||||
*/
|
|
||||||
.radio-content-field .radio.inline label span.text {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
/**
|
|
||||||
* holiday-weather.com - page is not scrollable
|
|
||||||
* Bug #1575011 - https://bugzilla.mozilla.org/show_bug.cgi?id=1575011
|
|
||||||
* WebCompat issue #18478 - https://webcompat.com/issues/18478
|
|
||||||
*
|
|
||||||
* the page won't scroll since the flex container is too high,
|
|
||||||
* adding min height to parent containers fixes the issue
|
|
||||||
*/
|
|
||||||
.page-container-style__pageContent {
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widgets-style__root {
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
/**
|
|
||||||
* dunkindonuts.com - form input fields are small and misaligned
|
|
||||||
* Bug #1575017 - https://bugzilla.mozilla.org/show_bug.cgi?id=1575017
|
|
||||||
* WebCompat issue #28742 - https://webcompat.com/issues/28742
|
|
||||||
*
|
|
||||||
* Form input fields are small and misaligned due to flex-basis: min-content;
|
|
||||||
* applied on their parent element. Setting it to auto fixes the issue
|
|
||||||
*/
|
|
||||||
.grid__item {
|
|
||||||
flex-basis: auto;
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
/**
|
|
||||||
* binance.com - can't see the full site
|
|
||||||
* Bug #1577270 - https://bugzilla.mozilla.org/show_bug.cgi?id=1577270
|
|
||||||
* WebCompat issue #17810 - https://webcompat.com/issues/17810
|
|
||||||
*
|
|
||||||
* The site does not have a doctype and is rendered in quirks mode. The calc() percentage
|
|
||||||
* height is applied on the .main-page .viewWrap element, but its parent does not have
|
|
||||||
* a specified height property. Adding a height of 100% to the parent fixes the issue.
|
|
||||||
*/
|
|
||||||
#tradeDiv {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
/**
|
|
||||||
* kitkat.com.au - can't see the content
|
|
||||||
* Bug #1577297 - https://bugzilla.mozilla.org/show_bug.cgi?id=1577297
|
|
||||||
* WebCompat issue #28992 - https://webcompat.com/issues/28992
|
|
||||||
*
|
|
||||||
* Affected element is too wide due to https://bugzilla.mozilla.org/show_bug.cgi?id=1316534.
|
|
||||||
* Adding min-width: 0; fixes the issue
|
|
||||||
*/
|
|
||||||
.columns .column.main {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
/* globals exportFunction */
|
|
||||||
|
|
||||||
Object.defineProperty(window.wrappedJSObject, "isTestFeatureSupported", {
|
|
||||||
get: exportFunction(function() {
|
|
||||||
return true;
|
|
||||||
}, window),
|
|
||||||
|
|
||||||
set: exportFunction(function() {}, window),
|
|
||||||
});
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bug 1452707 - Build site patch for ib.absa.co.za
|
|
||||||
* WebCompat issue #16401 - https://webcompat.com/issues/16401
|
|
||||||
*
|
|
||||||
* The online banking at ib.absa.co.za detect if window.controllers is a
|
|
||||||
* non-falsy value to detect if the current browser is Firefox or something
|
|
||||||
* else. In bug 1448045, this shim has been disabled for Firefox Nightly 61+,
|
|
||||||
* which breaks the UA detection on this site and results in a "Browser
|
|
||||||
* unsuppored" error message.
|
|
||||||
*
|
|
||||||
* This site patch simply sets window.controllers to a string, resulting in
|
|
||||||
* their check to work again.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* globals exportFunction */
|
|
||||||
|
|
||||||
console.info(
|
|
||||||
"window.controllers has been shimmed for compatibility reasons. See https://webcompat.com/issues/16401 for details."
|
|
||||||
);
|
|
||||||
|
|
||||||
Object.defineProperty(window.wrappedJSObject, "controllers", {
|
|
||||||
get: exportFunction(function() {
|
|
||||||
return true;
|
|
||||||
}, window),
|
|
||||||
|
|
||||||
set: exportFunction(function() {}, window),
|
|
||||||
});
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bug 1457335 - histography.io - Override UA & navigator.vendor
|
|
||||||
* WebCompat issue #1804 - https://webcompat.com/issues/1804
|
|
||||||
*
|
|
||||||
* This site is using a strict matching of navigator.userAgent and
|
|
||||||
* navigator.vendor to allow access for Safari or Chrome. Here, we set the
|
|
||||||
* values appropriately so we get recognized as Chrome.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* globals exportFunction */
|
|
||||||
|
|
||||||
console.info(
|
|
||||||
"The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/1804 for details."
|
|
||||||
);
|
|
||||||
|
|
||||||
const CHROME_UA = navigator.userAgent + " Chrome for WebCompat";
|
|
||||||
|
|
||||||
Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", {
|
|
||||||
get: exportFunction(function() {
|
|
||||||
return CHROME_UA;
|
|
||||||
}, window),
|
|
||||||
|
|
||||||
set: exportFunction(function() {}, window),
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(window.navigator.wrappedJSObject, "vendor", {
|
|
||||||
get: exportFunction(function() {
|
|
||||||
return "Google Inc.";
|
|
||||||
}, window),
|
|
||||||
|
|
||||||
set: exportFunction(function() {}, window),
|
|
||||||
});
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bug 1472075 - Build UA override for Bank of America for OSX & Linux
|
|
||||||
* WebCompat issue #2787 - https://webcompat.com/issues/2787
|
|
||||||
*
|
|
||||||
* BoA is showing a red warning to Linux and macOS users, while accepting
|
|
||||||
* Windows users without warning. From our side, there is no difference here
|
|
||||||
* and we receive a lot of user complains about the warnings, so we spoof
|
|
||||||
* as Firefox on Windows in those cases.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* globals exportFunction */
|
|
||||||
|
|
||||||
if (!navigator.platform.includes("Win")) {
|
|
||||||
console.info(
|
|
||||||
"The user agent has been overridden for compatibility reasons. See https://webcompat.com/issues/2787 for details."
|
|
||||||
);
|
|
||||||
|
|
||||||
const WINDOWS_UA = navigator.userAgent.replace(
|
|
||||||
/\(.*; rv:/i,
|
|
||||||
"(Windows NT 10.0; Win64; x64; rv:"
|
|
||||||
);
|
|
||||||
|
|
||||||
Object.defineProperty(window.navigator.wrappedJSObject, "userAgent", {
|
|
||||||
get: exportFunction(function() {
|
|
||||||
return WINDOWS_UA;
|
|
||||||
}, window),
|
|
||||||
|
|
||||||
set: exportFunction(function() {}, window),
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(window.navigator.wrappedJSObject, "appVersion", {
|
|
||||||
get: exportFunction(function() {
|
|
||||||
return "appVersion";
|
|
||||||
}, window),
|
|
||||||
|
|
||||||
set: exportFunction(function() {}, window),
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(window.navigator.wrappedJSObject, "platform", {
|
|
||||||
get: exportFunction(function() {
|
|
||||||
return "Win64";
|
|
||||||
}, window),
|
|
||||||
|
|
||||||
set: exportFunction(function() {}, window),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bug 1472081 - election.gov.np - Override window.sidebar with something falsey
|
|
||||||
* WebCompat issue #11622 - https://webcompat.com/issues/11622
|
|
||||||
*
|
|
||||||
* This site is blocking onmousedown and onclick if window.sidebar is something
|
|
||||||
* that evaluates to true, rendering the form fields unusable. This patch
|
|
||||||
* overrides window.sidebar with false, so the blocking event handlers won't
|
|
||||||
* get registered.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* globals exportFunction */
|
|
||||||
|
|
||||||
console.info(
|
|
||||||
"window.sidebar has been shimmed for compatibility reasons. See https://webcompat.com/issues/11622 for details."
|
|
||||||
);
|
|
||||||
|
|
||||||
Object.defineProperty(window.wrappedJSObject, "sidebar", {
|
|
||||||
get: exportFunction(function() {
|
|
||||||
return false;
|
|
||||||
}, window),
|
|
||||||
|
|
||||||
set: exportFunction(function() {}, window),
|
|
||||||
});
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* portalminasnet.com - Override window.sidebar with something falsey
|
|
||||||
* WebCompat issue #18143 - https://webcompat.com/issues/18143
|
|
||||||
*
|
|
||||||
* This site is blocking onmousedown and onclick if window.sidebar is something
|
|
||||||
* that evaluates to true, rendering the login unusable. This patch overrides
|
|
||||||
* window.sidebar with false, so the blocking event handlers won't get
|
|
||||||
* registered.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* globals exportFunction */
|
|
||||||
|
|
||||||
console.info(
|
|
||||||
"window.sidebar has been shimmed for compatibility reasons. See https://webcompat.com/issues/18143 for details."
|
|
||||||
);
|
|
||||||
|
|
||||||
Object.defineProperty(window.wrappedJSObject, "sidebar", {
|
|
||||||
get: exportFunction(function() {
|
|
||||||
return false;
|
|
||||||
}, window),
|
|
||||||
|
|
||||||
set: exportFunction(function() {}, window),
|
|
||||||
});
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue