mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
merge fx-team to mozilla-central a=merge
This commit is contained in:
commit
217c2b85c7
@ -249,6 +249,7 @@ EXTRA_JS_MODULES.commonjs.sdk.content += [
|
||||
'source/lib/sdk/content/loader.js',
|
||||
'source/lib/sdk/content/mod.js',
|
||||
'source/lib/sdk/content/page-mod.js',
|
||||
'source/lib/sdk/content/page-worker.js',
|
||||
'source/lib/sdk/content/sandbox.js',
|
||||
'source/lib/sdk/content/tab-events.js',
|
||||
'source/lib/sdk/content/thumbnail.js',
|
||||
|
@ -11,6 +11,7 @@ const { Ci } = require("chrome");
|
||||
const core = require("../l10n/core");
|
||||
const { loadSheet, removeSheet } = require("../stylesheet/utils");
|
||||
const { process, frames } = require("../remote/child");
|
||||
const { Services } = require("resource://gre/modules/Services.jsm");
|
||||
|
||||
const assetsURI = require('../self').data.url();
|
||||
|
||||
@ -80,7 +81,7 @@ function onDocumentReady2Translate(event) {
|
||||
}
|
||||
}
|
||||
|
||||
function onContentWindow({ target: document }) {
|
||||
function onContentWindow(document) {
|
||||
// Accept only HTML documents
|
||||
if (!(document instanceof Ci.nsIDOMHTMLDocument))
|
||||
return;
|
||||
@ -109,13 +110,20 @@ function onContentWindow({ target: document }) {
|
||||
|
||||
// Listen to creation of content documents in order to translate them as soon
|
||||
// as possible in their loading process
|
||||
const ON_CONTENT = "DOMDocElementInserted";
|
||||
const ON_CONTENT = "document-element-inserted";
|
||||
let enabled = false;
|
||||
function enable() {
|
||||
frames.addEventListener(ON_CONTENT, onContentWindow, true);
|
||||
if (enabled)
|
||||
return;
|
||||
Services.obs.addObserver(onContentWindow, ON_CONTENT, false);
|
||||
enabled = true;
|
||||
}
|
||||
process.port.on("sdk/l10n/html/enable", enable);
|
||||
|
||||
function disable() {
|
||||
frames.removeEventListener(ON_CONTENT, onContentWindow, true);
|
||||
if (!enabled)
|
||||
return;
|
||||
Services.obs.removeObserver(onContentWindow, ON_CONTENT);
|
||||
enabled = false;
|
||||
}
|
||||
process.port.on("sdk/l10n/html/disable", disable);
|
||||
|
153
addon-sdk/source/lib/sdk/content/page-worker.js
Normal file
153
addon-sdk/source/lib/sdk/content/page-worker.js
Normal file
@ -0,0 +1,153 @@
|
||||
/* 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 { frames } = require("../remote/child");
|
||||
const { Class } = require("../core/heritage");
|
||||
const { Disposable } = require('../core/disposable');
|
||||
const { data } = require("../self");
|
||||
const { once } = require("../dom/events");
|
||||
const { getAttachEventType } = require("./utils");
|
||||
const { Rules } = require('../util/rules');
|
||||
const { uuid } = require('../util/uuid');
|
||||
const { WorkerChild } = require("./worker-child");
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
const { observe } = require("../event/chrome");
|
||||
const { on } = require("../event/core");
|
||||
|
||||
const appShell = Cc["@mozilla.org/appshell/appShellService;1"].getService(Ci.nsIAppShellService);
|
||||
|
||||
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const pages = new Map();
|
||||
|
||||
const DOC_INSERTED = "document-element-inserted";
|
||||
|
||||
function isValidURL(page, url) {
|
||||
return !page.rules || page.rules.matchesAny(url);
|
||||
}
|
||||
|
||||
const ChildPage = Class({
|
||||
implements: [ Disposable ],
|
||||
setup: function(frame, id, options) {
|
||||
this.id = id;
|
||||
this.frame = frame;
|
||||
this.options = options;
|
||||
|
||||
this.webNav = appShell.createWindowlessBrowser(false);
|
||||
this.docShell.allowJavascript = this.options.allow.script;
|
||||
|
||||
// Accessing the browser's window forces the initial about:blank document to
|
||||
// be created before we start listening for notifications
|
||||
this.contentWindow;
|
||||
|
||||
this.webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
|
||||
|
||||
pages.set(this.id, this);
|
||||
|
||||
this.contentURL = options.contentURL;
|
||||
|
||||
if (options.include) {
|
||||
this.rules = Rules();
|
||||
this.rules.add.apply(this.rules, [].concat(options.include));
|
||||
}
|
||||
},
|
||||
|
||||
dispose: function() {
|
||||
pages.delete(this.id);
|
||||
this.webProgress.removeProgressListener(this);
|
||||
this.webNav = null;
|
||||
},
|
||||
|
||||
attachWorker: function() {
|
||||
if (!isValidURL(this, this.contentWindow.location.href))
|
||||
return;
|
||||
|
||||
this.options.id = uuid().toString();
|
||||
this.options.window = this.contentWindow;
|
||||
this.frame.port.emit("sdk/frame/connect", this.id, {
|
||||
id: this.options.id,
|
||||
url: this.contentWindow.document.documentURIObject.spec
|
||||
});
|
||||
new WorkerChild(this.options);
|
||||
},
|
||||
|
||||
get docShell() {
|
||||
return this.webNav.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell);
|
||||
},
|
||||
|
||||
get webProgress() {
|
||||
return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress);
|
||||
},
|
||||
|
||||
get contentWindow() {
|
||||
return this.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow);
|
||||
},
|
||||
|
||||
get contentURL() {
|
||||
return this.options.contentURL;
|
||||
},
|
||||
set contentURL(url) {
|
||||
this.options.contentURL = url;
|
||||
|
||||
url = this.options.contentURL ? data.url(this.options.contentURL) : "about:blank";
|
||||
this.webNav.loadURI(url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
|
||||
},
|
||||
|
||||
onLocationChange: function(progress, request, location, flags) {
|
||||
// Ignore inner-frame events
|
||||
if (progress != this.webProgress)
|
||||
return;
|
||||
// Ignore events that don't change the document
|
||||
if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)
|
||||
return;
|
||||
|
||||
let event = getAttachEventType(this.options);
|
||||
// Attaching at the start of the load is handled by the
|
||||
// document-element-inserted listener.
|
||||
if (event == DOC_INSERTED)
|
||||
return;
|
||||
|
||||
once(this.contentWindow, event, () => {
|
||||
this.attachWorker();
|
||||
}, false);
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"])
|
||||
});
|
||||
|
||||
on(observe(DOC_INSERTED), "data", ({ target }) => {
|
||||
let page = Array.from(pages.values()).find(p => p.contentWindow.document === target);
|
||||
if (!page)
|
||||
return;
|
||||
|
||||
if (getAttachEventType(page.options) == DOC_INSERTED)
|
||||
page.attachWorker();
|
||||
});
|
||||
|
||||
frames.port.on("sdk/frame/create", (frame, id, options) => {
|
||||
new ChildPage(frame, id, options);
|
||||
});
|
||||
|
||||
frames.port.on("sdk/frame/set", (frame, id, params) => {
|
||||
let page = pages.get(id);
|
||||
if (!page)
|
||||
return;
|
||||
|
||||
if ("allowScript" in params)
|
||||
page.docShell.allowJavascript = params.allowScript;
|
||||
if ("contentURL" in params)
|
||||
page.contentURL = params.contentURL;
|
||||
});
|
||||
|
||||
frames.port.on("sdk/frame/destroy", (frame, id) => {
|
||||
let page = pages.get(id);
|
||||
if (!page)
|
||||
return;
|
||||
|
||||
page.destroy();
|
||||
});
|
@ -53,7 +53,7 @@ function isWindowInTab(window) {
|
||||
if (isChildLoader) {
|
||||
let { frames } = require('../remote/child');
|
||||
let frame = frames.getFrameForWindow(window.top);
|
||||
return frame.isTab;
|
||||
return frame && frame.isTab;
|
||||
}
|
||||
else {
|
||||
// The deprecated sync worker API still does everything in the main process
|
||||
|
@ -51,7 +51,9 @@ const WorkerChild = Class({
|
||||
|
||||
this.sandbox = WorkerSandbox(this, this.window);
|
||||
|
||||
this.frozen = false;
|
||||
// If the document is still loading wait for it to finish before passing on
|
||||
// received messages
|
||||
this.frozen = this.window.document.readyState == "loading";
|
||||
this.frozenMessages = [];
|
||||
this.on('pageshow', () => {
|
||||
this.frozen = false;
|
||||
|
@ -57,38 +57,31 @@ function create(target, options) {
|
||||
frame.setAttribute('type', options.type || 'content');
|
||||
frame.setAttribute('src', options.uri || 'about:blank');
|
||||
|
||||
// Must set the remote attribute before attaching the frame to the document
|
||||
if (remote && isXUL) {
|
||||
// We remove XBL binding to avoid execution of code that is not going to
|
||||
// work because browser has no docShell attribute in remote mode
|
||||
// (for example)
|
||||
frame.setAttribute('style', '-moz-binding: none;');
|
||||
frame.setAttribute('remote', 'true');
|
||||
}
|
||||
|
||||
target.appendChild(frame);
|
||||
|
||||
// Load in separate process if `options.remote` is `true`.
|
||||
// http://mxr.mozilla.org/mozilla-central/source/content/base/src/nsFrameLoader.cpp#1347
|
||||
if (remote) {
|
||||
if (isXUL) {
|
||||
// We remove XBL binding to avoid execution of code that is not going to
|
||||
// work because browser has no docShell attribute in remote mode
|
||||
// (for example)
|
||||
frame.setAttribute('style', '-moz-binding: none;');
|
||||
frame.setAttribute('remote', 'true');
|
||||
}
|
||||
else {
|
||||
frame.QueryInterface(Ci.nsIMozBrowserFrame);
|
||||
frame.createRemoteFrameLoader(null);
|
||||
}
|
||||
if (remote && !isXUL) {
|
||||
frame.QueryInterface(Ci.nsIMozBrowserFrame);
|
||||
frame.createRemoteFrameLoader(null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// If browser is remote it won't have a `docShell`.
|
||||
if (!remote) {
|
||||
let docShell = getDocShell(frame);
|
||||
docShell.allowAuth = options.allowAuth || false;
|
||||
docShell.allowJavascript = options.allowJavascript || false;
|
||||
docShell.allowPlugins = options.allowPlugins || false;
|
||||
|
||||
// Control whether the document can move/resize the window. Requires
|
||||
// recently added platform capability, so we test to avoid exceptions
|
||||
// in cases where capability is not present yet.
|
||||
if ("allowWindowControl" in docShell && "allowWindowControl" in options)
|
||||
docShell.allowWindowControl = !!options.allowWindowControl;
|
||||
docShell.allowWindowControl = options.allowWindowControl || false;
|
||||
}
|
||||
|
||||
return frame;
|
||||
|
@ -8,48 +8,58 @@ module.metadata = {
|
||||
};
|
||||
|
||||
const { Class } = require('./core/heritage');
|
||||
const { on, emit, off, setListeners } = require('./event/core');
|
||||
const { filter, pipe, map, merge: streamMerge, stripListeners } = require('./event/utils');
|
||||
const { detach, attach, destroy, WorkerHost } = require('./content/utils');
|
||||
const { Worker } = require('./deprecated/sync-worker');
|
||||
const { ns } = require('./core/namespace');
|
||||
const { pipe, stripListeners } = require('./event/utils');
|
||||
const { connect, destroy, WorkerHost } = require('./content/utils');
|
||||
const { Worker } = require('./content/worker');
|
||||
const { Disposable } = require('./core/disposable');
|
||||
const { WeakReference } = require('./core/reference');
|
||||
const { EventTarget } = require('./event/target');
|
||||
const { unload } = require('./system/unload');
|
||||
const { events, streamEventsFrom } = require('./content/events');
|
||||
const { getAttachEventType } = require('./content/utils');
|
||||
const { setListeners } = require('./event/core');
|
||||
const { window } = require('./addon/window');
|
||||
const { getParentWindow } = require('./window/utils');
|
||||
const { create: makeFrame, getDocShell } = require('./frame/utils');
|
||||
const { contract } = require('./util/contract');
|
||||
const { contract: loaderContract } = require('./content/loader');
|
||||
const { has } = require('./util/array');
|
||||
const { Rules } = require('./util/rules');
|
||||
const { merge } = require('./util/object');
|
||||
const { data } = require('./self');
|
||||
const { getActiveView } = require("./view/core");
|
||||
const { uuid } = require('./util/uuid');
|
||||
const { useRemoteProcesses, remoteRequire, frames } = require("./remote/parent");
|
||||
remoteRequire("sdk/content/page-worker");
|
||||
|
||||
const views = new WeakMap();
|
||||
const workers = new WeakMap();
|
||||
const pages = new WeakMap();
|
||||
const pages = new Map();
|
||||
|
||||
const readyEventNames = [
|
||||
'DOMContentLoaded',
|
||||
'document-element-inserted',
|
||||
'load'
|
||||
];
|
||||
const internal = ns();
|
||||
|
||||
function workerFor(page) {
|
||||
return workers.get(page);
|
||||
}
|
||||
function pageFor(view) {
|
||||
return pages.get(view);
|
||||
}
|
||||
function viewFor(page) {
|
||||
return views.get(page);
|
||||
}
|
||||
function isDisposed (page) {
|
||||
return !views.get(page, false);
|
||||
let workerFor = (page) => workers.get(page);
|
||||
let isDisposed = (page) => !pages.has(internal(page).id);
|
||||
|
||||
// The frame is used to ensure we have a remote process to load workers in
|
||||
let remoteFrame = null;
|
||||
let framePromise = null;
|
||||
function getFrame() {
|
||||
if (framePromise)
|
||||
return framePromise;
|
||||
|
||||
framePromise = new Promise(resolve => {
|
||||
let view = makeFrame(window.document, {
|
||||
namespaceURI: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
||||
nodeName: "iframe",
|
||||
type: "content",
|
||||
remote: useRemoteProcesses,
|
||||
uri: "about:blank"
|
||||
});
|
||||
|
||||
// Wait for the remote side to connect
|
||||
let listener = (frame) => {
|
||||
if (frame.frameElement != view)
|
||||
return;
|
||||
frames.off("attach", listener);
|
||||
remoteFrame = frame;
|
||||
resolve(frame);
|
||||
}
|
||||
frames.on("attach", listener);
|
||||
});
|
||||
return framePromise;
|
||||
}
|
||||
|
||||
var pageContract = contract(merge({
|
||||
@ -64,7 +74,8 @@ var pageContract = contract(merge({
|
||||
is: ['string', 'array', 'regexp', 'undefined']
|
||||
},
|
||||
contentScriptWhen: {
|
||||
is: ['string', 'undefined']
|
||||
is: ['string', 'undefined'],
|
||||
map: (when) => when || "end"
|
||||
}
|
||||
}, loaderContract.rules));
|
||||
|
||||
@ -78,16 +89,18 @@ function disableScript (page) {
|
||||
|
||||
function Allow (page) {
|
||||
return {
|
||||
get script() { return getDocShell(viewFor(page)).allowJavascript; },
|
||||
set script(value) { return value ? enableScript(page) : disableScript(page); }
|
||||
};
|
||||
}
|
||||
get script() {
|
||||
return internal(page).options.allow.script;
|
||||
},
|
||||
set script(value) {
|
||||
internal(page).options.allow.script = value;
|
||||
|
||||
function injectWorker ({page}) {
|
||||
let worker = workerFor(page);
|
||||
let view = viewFor(page);
|
||||
if (isValidURL(page, view.contentDocument.URL))
|
||||
attach(worker, view.contentWindow);
|
||||
if (isDisposed(page))
|
||||
return;
|
||||
|
||||
remoteFrame.port.emit("sdk/frame/set", internal(page).id, { allowScript: value });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function isValidURL(page, url) {
|
||||
@ -97,31 +110,23 @@ function isValidURL(page, url) {
|
||||
const Page = Class({
|
||||
implements: [
|
||||
EventTarget,
|
||||
Disposable,
|
||||
WeakReference
|
||||
Disposable
|
||||
],
|
||||
extends: WorkerHost(workerFor),
|
||||
setup: function Page(options) {
|
||||
let page = this;
|
||||
options = pageContract(options);
|
||||
// Sanitize the options
|
||||
if ("contentScriptOptions" in options)
|
||||
options.contentScriptOptions = JSON.stringify(options.contentScriptOptions);
|
||||
|
||||
let uri = options.contentURL;
|
||||
internal(this).id = uuid().toString();
|
||||
internal(this).options = options;
|
||||
|
||||
let view = makeFrame(window.document, {
|
||||
nodeName: 'iframe',
|
||||
type: 'content',
|
||||
uri: uri ? data.url(uri) : '',
|
||||
allowJavascript: options.allow.script,
|
||||
allowPlugins: true,
|
||||
allowAuth: true
|
||||
});
|
||||
view.setAttribute('data-src', uri);
|
||||
for (let prop of ['contentScriptFile', 'contentScript', 'contentScriptWhen']) {
|
||||
this[prop] = options[prop];
|
||||
}
|
||||
|
||||
['contentScriptFile', 'contentScript', 'contentScriptWhen']
|
||||
.forEach(prop => page[prop] = options[prop]);
|
||||
|
||||
views.set(this, view);
|
||||
pages.set(view, this);
|
||||
pages.set(internal(this).id, this);
|
||||
|
||||
// Set listeners on the {Page} object itself, not the underlying worker,
|
||||
// like `onMessage`, as it gets piped
|
||||
@ -130,68 +135,60 @@ const Page = Class({
|
||||
workers.set(this, worker);
|
||||
pipe(worker, this);
|
||||
|
||||
if (this.include || options.include) {
|
||||
if (options.include) {
|
||||
this.rules = Rules();
|
||||
this.rules.add.apply(this.rules, [].concat(this.include || options.include));
|
||||
this.rules.add.apply(this.rules, [].concat(options.include));
|
||||
}
|
||||
|
||||
getFrame().then(frame => {
|
||||
if (isDisposed(this))
|
||||
return;
|
||||
|
||||
frame.port.emit("sdk/frame/create", internal(this).id, stripListeners(options));
|
||||
});
|
||||
},
|
||||
get allow() { return Allow(this); },
|
||||
set allow(value) {
|
||||
let allowJavascript = pageContract({ allow: value }).allow.script;
|
||||
return allowJavascript ? enableScript(this) : disableScript(this);
|
||||
if (isDisposed(this))
|
||||
return;
|
||||
this.allow.script = pageContract({ allow: value }).allow.script;
|
||||
},
|
||||
get contentURL() {
|
||||
return internal(this).options.contentURL;
|
||||
},
|
||||
get contentURL() { return viewFor(this).getAttribute("data-src") },
|
||||
set contentURL(value) {
|
||||
if (!isValidURL(this, value)) return;
|
||||
let view = viewFor(this);
|
||||
let contentURL = pageContract({ contentURL: value }).contentURL;
|
||||
if (!isValidURL(this, value))
|
||||
return;
|
||||
internal(this).options.contentURL = value;
|
||||
if (isDisposed(this))
|
||||
return;
|
||||
|
||||
// page-worker doesn't have a model like other APIs, so to be consitent
|
||||
// with the behavior "what you set is what you get", we need to store
|
||||
// the original `contentURL` given.
|
||||
// Even if XUL elements doesn't support `dataset`, properties, to
|
||||
// indicate that is a custom attribute the syntax "data-*" is used.
|
||||
view.setAttribute('data-src', contentURL);
|
||||
view.setAttribute('src', data.url(contentURL));
|
||||
remoteFrame.port.emit("sdk/frame/set", internal(this).id, { contentURL: value });
|
||||
},
|
||||
dispose: function () {
|
||||
if (isDisposed(this)) return;
|
||||
let view = viewFor(this);
|
||||
if (view.parentNode) view.parentNode.removeChild(view);
|
||||
views.delete(this);
|
||||
destroy(workers.get(this));
|
||||
if (isDisposed(this))
|
||||
return;
|
||||
pages.delete(internal(this).id);
|
||||
let worker = workerFor(this);
|
||||
if (worker)
|
||||
destroy(worker);
|
||||
remoteFrame.port.emit("sdk/frame/destroy", internal(this).id);
|
||||
|
||||
// Destroy the remote frame if all the pages have been destroyed
|
||||
if (pages.size == 0) {
|
||||
framePromise = null;
|
||||
remoteFrame.frameElement.remove();
|
||||
remoteFrame = null;
|
||||
}
|
||||
},
|
||||
toString: function () { return '[object Page]' }
|
||||
});
|
||||
|
||||
exports.Page = Page;
|
||||
|
||||
var pageEvents = streamMerge([events, streamEventsFrom(window)]);
|
||||
var readyEvents = filter(pageEvents, isReadyEvent);
|
||||
var formattedEvents = map(readyEvents, function({target, type}) {
|
||||
return { type: type, page: pageFromDoc(target) };
|
||||
frames.port.on("sdk/frame/connect", (frame, id, params) => {
|
||||
let page = pages.get(id);
|
||||
if (!page)
|
||||
return;
|
||||
connect(workerFor(page), frame, params);
|
||||
});
|
||||
var pageReadyEvents = filter(formattedEvents, function({page, type}) {
|
||||
return getAttachEventType(page) === type});
|
||||
on(pageReadyEvents, 'data', injectWorker);
|
||||
|
||||
function isReadyEvent ({type}) {
|
||||
return has(readyEventNames, type);
|
||||
}
|
||||
|
||||
/*
|
||||
* Takes a document, finds its doc shell tree root and returns the
|
||||
* matching Page instance if found
|
||||
*/
|
||||
function pageFromDoc(doc) {
|
||||
let parentWindow = getParentWindow(doc.defaultView), page;
|
||||
if (!parentWindow) return;
|
||||
|
||||
let frames = parentWindow.document.getElementsByTagName('iframe');
|
||||
for (let i = frames.length; i--;)
|
||||
if (frames[i].contentDocument === doc && (page = pageFor(frames[i])))
|
||||
return page;
|
||||
return null;
|
||||
}
|
||||
|
||||
getActiveView.define(Page, viewFor);
|
||||
|
@ -29,6 +29,11 @@ const options = require('@loader/options');
|
||||
const loaderModule = require('toolkit/loader');
|
||||
const { getTabForBrowser } = require('../tabs/utils');
|
||||
|
||||
const appInfo = Cc["@mozilla.org/xre/app-info;1"].
|
||||
getService(Ci.nsIXULRuntime);
|
||||
|
||||
exports.useRemoteProcesses = appInfo.browserTabsRemoteAutostart;
|
||||
|
||||
// Chose the right function for resolving relative a module id
|
||||
var moduleResolve;
|
||||
if (options.isNative) {
|
||||
@ -174,7 +179,10 @@ const Frame = Class({
|
||||
|
||||
this.port = new EventTarget();
|
||||
this.port.emit = (...args) => {
|
||||
ns(this).messageManager.sendAsyncMessage('sdk/remote/frame/message', {
|
||||
let manager = ns(this).messageManager;
|
||||
if (!manager)
|
||||
return;
|
||||
manager.sendAsyncMessage('sdk/remote/frame/message', {
|
||||
loaderID,
|
||||
args
|
||||
});
|
||||
|
@ -63,107 +63,6 @@ exports["test changing result from addon extras in panel"] = function(assert, do
|
||||
panel.port.emit("get-result");
|
||||
}
|
||||
|
||||
exports["test window result from addon extras in panel"] = function*(assert) {
|
||||
let loader = Loader(module, null, null, {
|
||||
modules: {
|
||||
"sdk/self": merge({}, self, {
|
||||
data: merge({}, self.data, {url: fixtures.url})
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const { Panel } = loader.require('sdk/panel');
|
||||
const { Page } = loader.require('sdk/page-worker');
|
||||
const { getActiveView } = loader.require("sdk/view/core");
|
||||
const { getDocShell } = loader.require('sdk/frame/utils');
|
||||
const { events } = loader.require("sdk/content/sandbox/events");
|
||||
const { on } = loader.require("sdk/event/core");
|
||||
const { isAddonContent } = loader.require("sdk/content/utils");
|
||||
|
||||
// make a page worker and wait for it to load
|
||||
var page = yield new Promise(resolve => {
|
||||
assert.pass("Creating the background page");
|
||||
|
||||
let page = Page({
|
||||
contentURL: "./test.html",
|
||||
contentScriptWhen: "end",
|
||||
contentScript: "self.port.emit('end', unsafeWindow.getTestURL() + '')"
|
||||
});
|
||||
|
||||
page.port.once("end", (url) => {
|
||||
assert.equal(url, fixtures.url("./test.html"), "url is correct");
|
||||
resolve(page);
|
||||
});
|
||||
});
|
||||
assert.pass("Created the background page");
|
||||
|
||||
var extrasVal = {
|
||||
test: function() {
|
||||
assert.pass("start test function");
|
||||
let frame = getActiveView(page);
|
||||
let window = getUnsafeWindow(frame.contentWindow);
|
||||
assert.equal(typeof window.getTestURL, "function", "window.getTestURL is a function");
|
||||
return window;
|
||||
}
|
||||
};
|
||||
|
||||
on(events, "content-script-before-inserted", ({ window, worker }) => {
|
||||
let url = window.location.href;
|
||||
assert.pass("content-script-before-inserted " + url);
|
||||
|
||||
if (isAddonContent({ contentURL: url })) {
|
||||
let extraStuff = Cu.cloneInto(extrasVal, window, {
|
||||
cloneFunctions: true
|
||||
});
|
||||
getUnsafeWindow(window).extras = extraStuff;
|
||||
|
||||
assert.pass("content-script-before-inserted done!");
|
||||
}
|
||||
});
|
||||
|
||||
let panel = Panel({
|
||||
contentURL: "./test-addon-extras-window.html"
|
||||
});
|
||||
|
||||
|
||||
yield new Promise(resolve => {
|
||||
panel.port.once("result1", (result) => {
|
||||
assert.equal(result, fixtures.url("./test.html"), "result1 is a window");
|
||||
resolve();
|
||||
});
|
||||
|
||||
assert.pass("emit get-result");
|
||||
panel.port.emit("get-result");
|
||||
});
|
||||
|
||||
page.destroy();
|
||||
|
||||
|
||||
page = yield new Promise(resolve => {
|
||||
let page = Page({
|
||||
contentURL: "./index.html",
|
||||
contentScriptWhen: "end",
|
||||
contentScript: "self.port.emit('end')"
|
||||
});
|
||||
page.port.once("end", () => resolve(page));
|
||||
});
|
||||
|
||||
|
||||
yield new Promise(resolve => {
|
||||
panel.port.once("result2", (result) => {
|
||||
assert.equal(result, fixtures.url("./index.html"), "result2 is a window");
|
||||
resolve();
|
||||
});
|
||||
|
||||
assert.pass("emit get-result");
|
||||
panel.port.emit("get-result");
|
||||
});
|
||||
|
||||
loader.unload();
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getUnsafeWindow (win) {
|
||||
return win.wrappedJSObject || win;
|
||||
}
|
||||
|
@ -327,19 +327,6 @@ exports.testAllowScript = function(assert, done) {
|
||||
" document.documentElement.getAttribute('foo') == 3)",
|
||||
contentScriptWhen: "ready"
|
||||
});
|
||||
|
||||
let frame = getActiveView(page);
|
||||
assert.equal(getDocShell(frame).allowJavascript, true, "allowJavascript is true");
|
||||
}
|
||||
|
||||
exports.testGetActiveViewAndDestroy = function(assert) {
|
||||
let page = Page({
|
||||
contentURL: "data:text/html;charset=utf-8,<title>test</title>"
|
||||
});
|
||||
let frame = getActiveView(page);
|
||||
assert.ok(frame.parentNode, "there is a parent node");
|
||||
page.destroy();
|
||||
assert.ok(!frame.parentNode, "there is not a parent node");
|
||||
}
|
||||
|
||||
exports.testPingPong = function(assert, done) {
|
||||
|
@ -1430,6 +1430,13 @@ pref("dom.identity.enabled", false);
|
||||
// Block insecure active content on https pages
|
||||
pref("security.mixed_content.block_active_content", true);
|
||||
|
||||
// Show degraded UI for http pages with password fields
|
||||
#ifdef NIGHTLY_BUILD
|
||||
pref("security.insecure_password.ui.enabled", true);
|
||||
#else
|
||||
pref("security.insecure_password.ui.enabled", false);
|
||||
#endif
|
||||
|
||||
// 1 = allow MITM for certificate pinning checks.
|
||||
pref("security.cert_pinning.enforcement_level", 1);
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
- 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>
|
||||
<title>&certerror.pagetitle1;</title>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutCertError.css" type="text/css" media="all" />
|
||||
<link rel="stylesheet" href="chrome://browser/content/certerror/aboutCertError.css" type="text/css" media="all" />
|
||||
<!-- This page currently uses the same favicon as neterror.xhtml.
|
||||
@ -51,6 +51,22 @@
|
||||
return decodeURIComponent(matches[1]);
|
||||
}
|
||||
|
||||
function goBack(buttonEl)
|
||||
{
|
||||
if (history.length > 1) {
|
||||
history.back();
|
||||
} else {
|
||||
location.href = "about:home";
|
||||
}
|
||||
buttonEl.disabled = true;
|
||||
}
|
||||
|
||||
function toggleVisibility(id)
|
||||
{
|
||||
var node = document.getElementById(id);
|
||||
node.style.visibility = node.style.visibility == "" ? "hidden" : "";
|
||||
}
|
||||
|
||||
function getDescription()
|
||||
{
|
||||
var url = document.documentURI;
|
||||
@ -66,31 +82,20 @@
|
||||
|
||||
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);
|
||||
for (let host of document.querySelectorAll(".hostname")) {
|
||||
host.textContent = document.location.hostname;
|
||||
}
|
||||
|
||||
var cssClass = getCSSClass();
|
||||
if (cssClass == "expertBadCert") {
|
||||
toggle('technicalContent');
|
||||
toggle('expertContent');
|
||||
toggleVisibility('advancedPanel');
|
||||
}
|
||||
|
||||
// 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 (cssClass == "badStsCert" || window != top) {
|
||||
document.getElementById("expertContent").setAttribute("hidden", "true");
|
||||
document.getElementById("advancedPanel").setAttribute("hidden", "true");
|
||||
}
|
||||
if (cssClass != "badStsCert") {
|
||||
document.getElementById("badStsCertExplanation").setAttribute("hidden", "true");
|
||||
@ -167,7 +172,7 @@
|
||||
* domain names are famous for having '.' characters in them,
|
||||
* which would allow spurious and possibly hostile matches.
|
||||
*/
|
||||
if (endsWith(okHost, "." + thisHost))
|
||||
if (okHost.endsWith("." + thisHost))
|
||||
link.href = proto + okHost;
|
||||
|
||||
/* case #2:
|
||||
@ -175,68 +180,46 @@
|
||||
*
|
||||
* The certificate is only valid for garage.maemo.org
|
||||
*/
|
||||
if (endsWith(thisHost, "." + okHost))
|
||||
if (thisHost.endsWith("." + okHost))
|
||||
link.href = proto + okHost;
|
||||
|
||||
// If we set a link, meaning there's something helpful for
|
||||
// the user here, expand the section by default
|
||||
if (link.href && getCSSClass() != "expertBadCert")
|
||||
toggle("technicalContent");
|
||||
}
|
||||
|
||||
function endsWith(haystack, needle) {
|
||||
return haystack.slice(-needle.length) == needle;
|
||||
}
|
||||
|
||||
function toggle(id) {
|
||||
var el = document.getElementById(id);
|
||||
if (el.getAttribute("collapsed"))
|
||||
el.removeAttribute("collapsed");
|
||||
else
|
||||
el.setAttribute("collapsed", true);
|
||||
toggleVisibility("advancedPanel");
|
||||
}
|
||||
]]></script>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
|
||||
<!-- PAGE CONTAINER (for styling purposes only) -->
|
||||
<div id="errorPageContainer">
|
||||
|
||||
<!-- Error Title -->
|
||||
<div id="errorTitle">
|
||||
<h1 id="errorTitleText">&certerror.longpagetitle;</h1>
|
||||
<h1 id="errorTitleText">&certerror.longpagetitle1;</h1>
|
||||
</div>
|
||||
|
||||
<!-- LONG CONTENT (the section most likely to require scrolling) -->
|
||||
<div id="errorLongContent">
|
||||
<div id="introContent">
|
||||
<p id="introContentP1">&certerror.introPara1;</p>
|
||||
<p>&certerror.introPara2;</p>
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorShortDesc">
|
||||
<p>&certerror.introPara;</p>
|
||||
</div>
|
||||
|
||||
<div id="whatShouldIDoContent">
|
||||
<h2>&certerror.whatShouldIDo.heading;</h2>
|
||||
<div id="whatShouldIDoContentText">
|
||||
<p>&certerror.whatShouldIDo.content;</p>
|
||||
<p id="badStsCertExplanation">&certerror.whatShouldIDo.badStsCertExplanation;</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 -->
|
||||
<h2 id="technicalContent" class="expander" collapsed="true">
|
||||
<button onclick="toggle('technicalContent');">&certerror.technical.heading;</button>
|
||||
</h2>
|
||||
<p id="technicalContentText"/>
|
||||
|
||||
<h2 id="expertContent" class="expander" collapsed="true">
|
||||
<button onclick="toggle('expertContent');">&certerror.expert.heading;</button>
|
||||
</h2>
|
||||
<p id="badStsCertExplanation">&certerror.whatShouldIDo.badStsCertExplanation;</p>
|
||||
<div>
|
||||
<p>&certerror.expert.content;</p>
|
||||
<p>&certerror.expert.contentPara2;</p>
|
||||
<p><a href="https://support.mozilla.org/kb/tls-error-reports" id="learnMoreLink" target="new">&certerror.learnMore;</a></p>
|
||||
</div>
|
||||
|
||||
<div id="buttonContainer">
|
||||
<button id="returnButton" autocomplete="off" onclick="goBack(this);" autofocus="true">&certerror.returnToPreviousPage.label;</button>
|
||||
<div id="buttonSpacer"></div>
|
||||
<button id="advancedButton" autocomplete="off" onclick="toggleVisibility('advancedPanel');" autofocus="true">&certerror.advanced.label;</button>
|
||||
</div>
|
||||
<!-- Advanced panel, which is hidden by default -->
|
||||
<div id="advancedPanel" style="visibility: hidden;">
|
||||
<p id="technicalContentText"/>
|
||||
<button id='exceptionDialogButton'>&certerror.addException.label;</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6927,6 +6927,13 @@ var gIdentityHandler = {
|
||||
return this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT;
|
||||
},
|
||||
|
||||
get _hasInsecureLoginForms() {
|
||||
// checks if the page has been flagged for an insecure login. Also checks
|
||||
// if the pref to degrade the UI is set to true
|
||||
return LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser) &&
|
||||
Services.prefs.getBoolPref("security.insecure_password.ui.enabled");
|
||||
},
|
||||
|
||||
// smart getters
|
||||
get _identityPopup () {
|
||||
delete this._identityPopup;
|
||||
@ -7257,7 +7264,7 @@ var gIdentityHandler = {
|
||||
this._identityBox.classList.add("weakCipher");
|
||||
}
|
||||
}
|
||||
if (LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser)) {
|
||||
if (this._hasInsecureLoginForms) {
|
||||
// Insecure login forms can only be present on "unknown identity"
|
||||
// pages, either already insecure or with mixed active content loaded.
|
||||
this._identityBox.classList.add("insecureLoginForms");
|
||||
@ -7301,7 +7308,7 @@ var gIdentityHandler = {
|
||||
|
||||
// Determine if there are insecure login forms.
|
||||
let loginforms = "secure";
|
||||
if (LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser)) {
|
||||
if (this._hasInsecureLoginForms) {
|
||||
loginforms = "insecure";
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
head.js
|
||||
file_dom_notifications.html
|
||||
|
||||
[browser_notification_close.js]
|
||||
[browser_notification_do_not_disturb.js]
|
||||
[browser_notification_open_settings.js]
|
||||
[browser_notification_remove_permission.js]
|
||||
[browser_notification_permission_migration.js]
|
||||
|
||||
[browser_notification_tab_switching.js]
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug 1100662 - content access causing uncaught exception - Error: cannot ipc non-cpow object at chrome://mochitests/content/browser/browser/base/content/test/general/browser_notification_tab_switching.js:32 (or in RemoteAddonsChild.jsm)
|
||||
|
@ -2,17 +2,6 @@
|
||||
|
||||
var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
|
||||
|
||||
function waitForCloseWindow(window) {
|
||||
return new Promise(function(resolve) {
|
||||
Services.ww.registerNotification(function observer(subject, topic, data) {
|
||||
if (topic == "domwindowclosed" && subject == window) {
|
||||
Services.ww.unregisterNotification(observer);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* test_settingsOpen_observer() {
|
||||
info("Opening a dummy tab so openPreferences=>switchToTabHavingURI doesn't use the blank tab.");
|
||||
yield BrowserTestUtils.withNewTab({
|
||||
@ -52,7 +41,7 @@ add_task(function* test_settingsOpen_button() {
|
||||
return;
|
||||
}
|
||||
|
||||
let closePromise = waitForCloseWindow(alertWindow);
|
||||
let closePromise = promiseWindowClosed(alertWindow);
|
||||
let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "about:preferences#content");
|
||||
let openSettingsMenuItem = alertWindow.document.getElementById("openSettingsMenuItem");
|
||||
openSettingsMenuItem.click();
|
||||
|
@ -0,0 +1,43 @@
|
||||
const UI_VERSION = 32;
|
||||
|
||||
var gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"]
|
||||
.getService(Ci.nsIObserver);
|
||||
var notificationURI = makeURI("http://example.org");
|
||||
var pm = Services.perms;
|
||||
var currentUIVersion;
|
||||
|
||||
add_task(function* setup() {
|
||||
currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
|
||||
Services.prefs.setIntPref("browser.migration.version", UI_VERSION - 1);
|
||||
pm.add(notificationURI, "desktop-notification", pm.ALLOW_ACTION);
|
||||
});
|
||||
|
||||
add_task(function* test_permissionMigration() {
|
||||
if ("@mozilla.org/system-alerts-service;1" in Cc) {
|
||||
ok(true, "Notifications don't use XUL windows on all platforms.");
|
||||
return;
|
||||
}
|
||||
|
||||
info("Waiting for migration notification");
|
||||
let alertWindowPromise = promiseAlertWindow();
|
||||
gBrowserGlue.observe(null, "browser-glue-test", "force-ui-migration");
|
||||
let alertWindow = yield alertWindowPromise;
|
||||
|
||||
info("Clicking on notification");
|
||||
let url = Services.urlFormatter.formatURLPref("browser.push.warning.infoURL");
|
||||
let closePromise = promiseWindowClosed(alertWindow);
|
||||
let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, url);
|
||||
EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"), {}, alertWindow);
|
||||
|
||||
info("Waiting for migration info tab");
|
||||
let tab = yield tabPromise;
|
||||
ok(tab, "The migration info tab opened");
|
||||
|
||||
yield closePromise;
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(function* cleanup() {
|
||||
Services.prefs.setIntPref("browser.migration.version", currentUIVersion);
|
||||
pm.remove(notificationURI, "desktop-notification");
|
||||
});
|
31
browser/base/content/test/alerts/head.js
Normal file
31
browser/base/content/test/alerts/head.js
Normal file
@ -0,0 +1,31 @@
|
||||
function promiseAlertWindow() {
|
||||
return new Promise(function(resolve) {
|
||||
let listener = {
|
||||
onOpenWindow(window) {
|
||||
let alertWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
|
||||
alertWindow.addEventListener("load", function onLoad() {
|
||||
alertWindow.removeEventListener("load", onLoad);
|
||||
let windowType = alertWindow.document.documentElement.getAttribute("windowtype");
|
||||
if (windowType == "alert:alert") {
|
||||
Services.wm.removeListener(listener);
|
||||
resolve(alertWindow);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
Services.wm.addListener(listener);
|
||||
});
|
||||
}
|
||||
|
||||
// `promiseWindowClosed` is similar to `BrowserTestUtils.closeWindow`, but
|
||||
// doesn't call `window.close()`.
|
||||
function promiseWindowClosed(window) {
|
||||
return new Promise(function(resolve) {
|
||||
Services.ww.registerNotification(function observer(subject, topic, data) {
|
||||
if (topic == "domwindowclosed" && subject == window) {
|
||||
Services.ww.unregisterNotification(observer);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -122,6 +122,7 @@ skip-if = (os == "linux" || os == "mac") && debug # bug 970052, bug 970053
|
||||
skip-if = os == "linux" # Bug 958026
|
||||
support-files =
|
||||
content_aboutAccounts.js
|
||||
[browser_aboutCertError.js]
|
||||
[browser_aboutSupport_newtab_security_state.js]
|
||||
[browser_aboutHealthReport.js]
|
||||
skip-if = os == "linux" # Bug 924307
|
||||
|
79
browser/base/content/test/general/browser_aboutCertError.js
Normal file
79
browser/base/content/test/general/browser_aboutCertError.js
Normal file
@ -0,0 +1,79 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// This is testing the aboutCertError page (Bug 1207107). It's a start,
|
||||
// but should be expanded to include cert_domain_link / badStsCert
|
||||
|
||||
const GOOD_PAGE = "https://example.com/";
|
||||
const BAD_CERT = "https://expired.example.com/";
|
||||
|
||||
add_task(function* checkReturnToAboutHome() {
|
||||
info("Loading a bad cert page directly and making sure 'return to previous page' goes to about:home");
|
||||
let browser;
|
||||
let certErrorLoaded;
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => {
|
||||
gBrowser.selectedTab = gBrowser.addTab(BAD_CERT);
|
||||
browser = gBrowser.selectedBrowser;
|
||||
certErrorLoaded = waitForCertErrorLoad(browser);
|
||||
}, false);
|
||||
|
||||
info("Loading and waiting for the cert error");
|
||||
yield certErrorLoaded;
|
||||
|
||||
is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack");
|
||||
is(browser.webNavigation.canGoForward, false, "!webNavigation.canGoForward");
|
||||
|
||||
info("Clicking the go back button on about:certerror");
|
||||
let pageshowPromise = promiseWaitForEvent(browser, "pageshow");
|
||||
yield ContentTask.spawn(browser, null, function* () {
|
||||
let doc = content.document;
|
||||
let returnButton = doc.getElementById("returnButton");
|
||||
returnButton.click();
|
||||
});
|
||||
yield pageshowPromise;
|
||||
|
||||
is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack");
|
||||
is(browser.webNavigation.canGoForward, false, "!webNavigation.canGoForward");
|
||||
is(gBrowser.currentURI.spec, "about:home", "Went back");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
add_task(function* checkReturnToPreviousPage() {
|
||||
info("Loading a bad cert page and making sure 'return to previous page' goes back");
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, GOOD_PAGE);
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
|
||||
info("Loading and waiting for the cert error");
|
||||
let certErrorLoaded = waitForCertErrorLoad(browser);
|
||||
BrowserTestUtils.loadURI(browser, BAD_CERT);
|
||||
yield certErrorLoaded;
|
||||
|
||||
is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack");
|
||||
is(browser.webNavigation.canGoForward, false, "!webNavigation.canGoForward");
|
||||
|
||||
info("Clicking the go back button on about:certerror");
|
||||
let pageshowPromise = promiseWaitForEvent(browser, "pageshow");
|
||||
yield ContentTask.spawn(browser, null, function* () {
|
||||
let doc = content.document;
|
||||
let returnButton = doc.getElementById("returnButton");
|
||||
returnButton.click();
|
||||
});
|
||||
yield pageshowPromise;
|
||||
|
||||
is(browser.webNavigation.canGoBack, false, "!webNavigation.canGoBack");
|
||||
is(browser.webNavigation.canGoForward, true, "webNavigation.canGoForward");
|
||||
is(gBrowser.currentURI.spec, GOOD_PAGE, "Went back");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function waitForCertErrorLoad(browser) {
|
||||
return new Promise(resolve => {
|
||||
info("Waiting for DOMContentLoaded event");
|
||||
browser.addEventListener("DOMContentLoaded", function load() {
|
||||
browser.removeEventListener("DOMContentLoaded", load, false, true);
|
||||
resolve();
|
||||
}, false, true);
|
||||
});
|
||||
}
|
@ -9,15 +9,17 @@ function test() {
|
||||
}
|
||||
|
||||
function testBrokenCert() {
|
||||
if (gBrowser.contentDocument.documentURI === "about:blank")
|
||||
return;
|
||||
window.removeEventListener("DOMContentLoaded", testBrokenCert, true);
|
||||
|
||||
// Confirm that we are displaying the contributed error page, not the default
|
||||
ok(gBrowser.contentDocument.documentURI.startsWith("about:certerror"), "Broken page should go to about:certerror, not about:neterror");
|
||||
|
||||
// Confirm that the expert section is collapsed
|
||||
var expertDiv = gBrowser.contentDocument.getElementById("expertContent");
|
||||
ok(expertDiv, "Expert content div should exist");
|
||||
ok(expertDiv.hasAttribute("collapsed"), "Expert content should be collapsed by default");
|
||||
var advancedDiv = gBrowser.contentDocument.getElementById("advancedPanel");
|
||||
ok(advancedDiv, "Advanced content div should exist");
|
||||
is_element_hidden(advancedDiv, "Advanced content should not be visible by default");
|
||||
|
||||
// Tweak the expert mode pref
|
||||
gPrefService.setBoolPref("browser.xul.error_pages.expert_bad_cert", true);
|
||||
@ -28,10 +30,9 @@ function testBrokenCert() {
|
||||
|
||||
function testExpertPref() {
|
||||
window.removeEventListener("DOMContentLoaded", testExpertPref, true);
|
||||
var expertDiv = gBrowser.contentDocument.getElementById("expertContent");
|
||||
var technicalDiv = gBrowser.contentDocument.getElementById("technicalContent");
|
||||
ok(!expertDiv.hasAttribute("collapsed"), "Expert content should not be collapsed with the expert mode pref set");
|
||||
ok(!technicalDiv.hasAttribute("collapsed"), "Technical content should not be collapsed with the expert mode pref set");
|
||||
var advancedDiv = gBrowser.contentDocument.getElementById("advancedPanel");
|
||||
ok(advancedDiv, "Advanced content div should exist");
|
||||
is_element_visible(advancedDiv, "Advanced content should be visible by default");
|
||||
|
||||
// Clean up
|
||||
gBrowser.removeCurrentTab();
|
||||
|
@ -17,12 +17,12 @@ function testIframeCert(e) {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", testIframeCert, true);
|
||||
// Confirm that the expert section is hidden
|
||||
var doc = gBrowser.contentDocument.getElementsByTagName('iframe')[0].contentDocument;
|
||||
var eC = doc.getElementById("expertContent");
|
||||
ok(eC, "Expert content should exist")
|
||||
ok(eC.hasAttribute("hidden"), "Expert content should be hidded by default");
|
||||
var aP = doc.getElementById("advancedPanel");
|
||||
ok(aP, "Advanced content should exist");
|
||||
is_element_hidden(aP, "Advanced content should not be visible by default")
|
||||
|
||||
// Clean up
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
|
||||
finish();
|
||||
}
|
||||
|
@ -18,6 +18,10 @@ function waitForInsecureLoginFormsStateChange(browser, count) {
|
||||
* Checks the insecure login forms logic for the identity block.
|
||||
*/
|
||||
add_task(function* test_simple() {
|
||||
yield new Promise(resolve => SpecialPowers.pushPrefEnv({
|
||||
"set": [["security.insecure_password.ui.enabled", true]],
|
||||
}, resolve));
|
||||
|
||||
for (let scheme of ["http", "https"]) {
|
||||
let tab = gBrowser.addTab(scheme + testUrlPath + "form_basic.html");
|
||||
let browser = tab.linkedBrowser;
|
||||
|
@ -42,9 +42,9 @@
|
||||
*
|
||||
*/
|
||||
function require(url) {
|
||||
var devTimeUrl = url.replace(/^script!/, '');
|
||||
var devTimeUrl = devTimeUrl.replace(/^imports.*!/, '');
|
||||
var devTimeUrl = devTimeUrl.replace(/^exports.*!/, '');
|
||||
// strip off everything up to and including the last !, as those are
|
||||
// all directives to webpack
|
||||
var devTimeUrl = url.replace(/^.*!/, '');
|
||||
insertScript(devTimeUrl, true);
|
||||
}
|
||||
|
||||
@ -174,16 +174,60 @@
|
||||
// We don't use the SDK's CSS. This will prevent spurious 404 errors.
|
||||
window.OTProperties.cssURL = "about:blank";
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Explicitly create this here so that it's deterministic and easier to
|
||||
// understand, rather than having it created as a side-effect of loading
|
||||
// one of our faux-modules.
|
||||
var loop = {};
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="config.js"></script>
|
||||
|
||||
<!-- note that for 'make dist', webappEntryPoint.js is replaced by -->
|
||||
<!-- the standalone.js bundle -->
|
||||
<script>
|
||||
// Wait for both "localized" to fire and webapp.js to finish executing.
|
||||
// This needs to be available ahead of either of those things happening,
|
||||
// so we declare it here.
|
||||
//
|
||||
// The localized event fires asynchronously after l10n.js loads,
|
||||
// so it could happen before or after loop.webapp.init has been
|
||||
// defined.
|
||||
//
|
||||
// This means at least in the (current) dev setup where our
|
||||
// require shim injects script tags dynamically. The race could conceivably
|
||||
// happen in production as well, after l10n.js loads and before webpack.js
|
||||
// is loaded.
|
||||
//
|
||||
// Fortunately, that dev setup nastiness will go away once we get to a
|
||||
// single build setup using real modules and obsolete build-jsx.
|
||||
var localizedHasFired = false;
|
||||
function initIfReady() {
|
||||
if (localizedHasFired && "webapp" in loop) {
|
||||
loop.webapp.init();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- We'd like to bundle/minify this at some point, but we need to work
|
||||
out how to configure webpack to run this in a module container
|
||||
rather than directly in the global scope. -->
|
||||
|
||||
<!-- Note that as long as the various closures in the files loaded by
|
||||
webppEntryPoint.js take navigator.mozL10n as an argument, this
|
||||
JS must run to completion before that file loads, so this one must be
|
||||
loaded synchronously first -->
|
||||
<script type="text/javascript" src="libs/l10n-gaia-02ca67948fe8.js">
|
||||
</script>
|
||||
|
||||
<!-- Note that for 'make dist', webappEntryPoint.js is replaced by
|
||||
the standalone.js bundle -->
|
||||
<script type="text/javascript" src="webappEntryPoint.js"></script>
|
||||
|
||||
<script>
|
||||
// Wait for all the localization notes to load
|
||||
window.addEventListener("localized", function() {
|
||||
loop.webapp.init();
|
||||
// see the initIfReady() comments in this file for details
|
||||
localizedHasFired = true;
|
||||
initIfReady();
|
||||
}, false);
|
||||
</script>
|
||||
|
||||
|
@ -246,3 +246,10 @@ loop.webapp = (function(_, OT, mozL10n) {
|
||||
WebappRootView: WebappRootView
|
||||
};
|
||||
})(_, window.OT, navigator.mozL10n);
|
||||
|
||||
// This should only be available when this file is loaded into index.html,
|
||||
// where it's used to actually start the app.
|
||||
/* global initIfReady */
|
||||
if (typeof initIfReady === "function") {
|
||||
initIfReady();
|
||||
}
|
||||
|
@ -246,3 +246,10 @@ loop.webapp = (function(_, OT, mozL10n) {
|
||||
WebappRootView: WebappRootView
|
||||
};
|
||||
})(_, window.OT, navigator.mozL10n);
|
||||
|
||||
// This should only be available when this file is loaded into index.html,
|
||||
// where it's used to actually start the app.
|
||||
/* global initIfReady */
|
||||
if (typeof initIfReady === "function") {
|
||||
initIfReady();
|
||||
}
|
||||
|
@ -19,8 +19,6 @@
|
||||
// we turn that off by forcing require to false in that context.
|
||||
require("imports?require=>false!shared/libs/sdk.js");
|
||||
|
||||
require("script!./libs/l10n-gaia-02ca67948fe8.js");
|
||||
|
||||
// Ultimately, we'll likely want to pull the vendor libraries from npm, as that
|
||||
// makes upgrading easier, and it's generally better practice to minify the
|
||||
// "source" versions of libraries rather than built artifacts. We probably do
|
||||
@ -28,29 +26,64 @@ require("script!./libs/l10n-gaia-02ca67948fe8.js");
|
||||
// elimination, but that can be a bit of judgement call.
|
||||
require("exports?_!shared/libs/lodash-3.9.3.js");
|
||||
|
||||
// Note that anything that uses the script loader doesn't get minified, so
|
||||
// these need to be shimmed to use to other loaders (easiest first cut) or
|
||||
// turned into real modules:
|
||||
require("script!shared/libs/backbone-1.2.1.js");
|
||||
require("script!shared/libs/react-0.12.2.js");
|
||||
// Disable Backbone's AMD auto-detection, as described at:
|
||||
//
|
||||
// https://github.com/jashkenas/backbone/wiki/Using-Backbone-without-jQuery
|
||||
//
|
||||
require("expose?Backbone!imports?define=>false!shared/libs/backbone-1.2.1.js");
|
||||
|
||||
require("script!shared/js/utils.js");
|
||||
require("script!shared/js/crypto.js");
|
||||
require("script!shared/js/mixins.js");
|
||||
require("script!shared/js/actions.js");
|
||||
require("script!shared/js/validate.js");
|
||||
require("script!shared/js/dispatcher.js");
|
||||
require("script!shared/js/otSdkDriver.js");
|
||||
require("script!shared/js/store.js");
|
||||
require("script!shared/js/activeRoomStore.js");
|
||||
require("script!shared/js/views.js");
|
||||
require("script!shared/js/urlRegExps.js");
|
||||
require("script!shared/js/textChatStore.js");
|
||||
require("script!shared/js/textChatView.js");
|
||||
require("script!shared/js/linkifiedTextView.js");
|
||||
/* global: __PROD__ */
|
||||
if (typeof __PROD__ !== "undefined") {
|
||||
// webpack warns if we try to minify some prebuilt libraries, so we
|
||||
// pull in the unbuilt version from node_modules
|
||||
require("expose?React!react");
|
||||
require("expose?React!react/addons");
|
||||
} else {
|
||||
// our development server setup doesn't yet handle real modules, so for now...
|
||||
require("shared/libs/react-0.12.2.js");
|
||||
}
|
||||
|
||||
require("script!./js/standaloneAppStore.js");
|
||||
require("script!./js/standaloneMozLoop.js");
|
||||
require("script!./js/standaloneRoomViews.js");
|
||||
require("script!./js/standaloneMetricsStore.js");
|
||||
require("script!./js/webapp.js");
|
||||
|
||||
// Someday, these will be real modules. For now, we're chaining loaders
|
||||
// to teach webpack how to treat them like modules anyway.
|
||||
//
|
||||
// We do it in this file rather than globally in webpack.config.js
|
||||
// because:
|
||||
//
|
||||
// * it's easiest to understand magic (like loader chaining
|
||||
// interactions) when it's explicit and in one place
|
||||
// * migrating stuff over to real modules is easier to do gradually
|
||||
//
|
||||
// The strategy works like this (webpack loaders chain from right to left):
|
||||
//
|
||||
// In standalone, loop is defined for the first time in index.html.
|
||||
//
|
||||
// The exports?loop loader sets up webpack to ensure that exports is
|
||||
// actually exposed to outside world, rather than held privately.
|
||||
//
|
||||
// The imports=?loop=>window.loop loader puts the existing window.loop
|
||||
// reference into that exported container for further modification.
|
||||
//
|
||||
// See https://webpack.github.io/docs/shimming-modules.html for more
|
||||
// context.
|
||||
//
|
||||
require("imports?loop=>window.loop!exports?loop!shared/js/utils.js");
|
||||
require("imports?this=>window,loop=>window.loop!exports?loop!shared/js/crypto.js");
|
||||
require("imports?loop=>window.loop!exports?loop!shared/js/mixins.js");
|
||||
require("imports?loop=>window.loop!exports?loop!shared/js/actions.js");
|
||||
require("imports?loop=>window.loop!exports?loop!shared/js/validate.js");
|
||||
require("imports?loop=>window.loop!exports?loop!shared/js/dispatcher.js");
|
||||
require("imports?loop=>window.loop!exports?loop!shared/js/otSdkDriver.js");
|
||||
require("imports?loop=>window.loop!exports?loop!shared/js/store.js");
|
||||
require("imports?loop=>window.loop!exports?loop!shared/js/activeRoomStore.js");
|
||||
require("imports?loop=>window.loop!exports?loop!shared/js/views.js");
|
||||
require("imports?loop=>window.loop!exports?loop!shared/js/urlRegExps.js");
|
||||
require("imports?loop=>window.loop!exports?loop!shared/js/textChatStore.js");
|
||||
require("imports?loop=>window.loop!exports?loop!shared/js/textChatView.js");
|
||||
require("imports?loop=>window.loop!exports?loop!shared/js/linkifiedTextView.js");
|
||||
|
||||
require("imports?loop=>window.loop!exports?loop!./js/standaloneAppStore.js");
|
||||
require("imports?loop=>window.loop!exports?loop!./js/standaloneMozLoop.js");
|
||||
require("imports?loop=>window.loop!exports?loop!./js/standaloneRoomViews.js");
|
||||
require("imports?loop=>window.loop!exports?loop!./js/standaloneMetricsStore.js");
|
||||
require("imports?loop=>window.loop!exports?loop!./js/webapp.js");
|
||||
|
@ -17,8 +17,10 @@
|
||||
"eslint-plugin-mozilla": "../../../../testing/eslint-plugin-mozilla",
|
||||
"eslint-plugin-react": "3.5.x",
|
||||
"exports-loader": "0.6.x",
|
||||
"expose-loader": "0.7.x",
|
||||
"express": "4.x",
|
||||
"imports-loader": "0.6.x",
|
||||
"react": "0.12.x",
|
||||
"script-loader": "0.6.x",
|
||||
"webpack": "1.12.x"
|
||||
},
|
||||
|
@ -37,8 +37,17 @@ function getSharedDir() {
|
||||
module.exports = {
|
||||
entry: "./content/webappEntryPoint.js",
|
||||
|
||||
// These symbols come in either via <script> tags or the expose loader,
|
||||
// so we tell webpack to allow them to be unresolved
|
||||
externals: {
|
||||
"Backbone": "Backbone",
|
||||
"navigator.mozL10n": "navigator.mozL10n",
|
||||
"React": "React",
|
||||
"underscore": "_"
|
||||
},
|
||||
|
||||
// We want the shared modules to be available without the requirer needing
|
||||
// to know the path to them, especially that path is different in
|
||||
// to know the path to them, especially since that path is different in
|
||||
// mozilla-central and loop-client.
|
||||
resolve: {
|
||||
alias: {
|
||||
@ -55,6 +64,21 @@ module.exports = {
|
||||
},
|
||||
|
||||
plugins: [
|
||||
// This is a soft-dependency for Backbone, and since we don't use it,
|
||||
// we need to tell webpack to ignore the unresolved reference. Cribbed
|
||||
// from
|
||||
// https://github.com/jashkenas/backbone/wiki/Using-Backbone-without-jQuery
|
||||
new webpack.IgnorePlugin(/^jquery$/),
|
||||
|
||||
// definePlugin takes raw strings and inserts them, so we can put a
|
||||
// JS string for evaluation at build time. Right now, we test NODE_ENV
|
||||
// to so we can do different things at build time based on whether we're
|
||||
// in production mode.
|
||||
new webpack.DefinePlugin({
|
||||
__PROD__: JSON.stringify(JSON.parse(
|
||||
process.env.NODE_ENV && process.env.NODE_ENV === "production"))
|
||||
}),
|
||||
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
// XXX I'd _like_ to only suppress the warnings for vendor code, since
|
||||
|
@ -582,11 +582,53 @@ this.MigrationUtils = Object.freeze({
|
||||
}
|
||||
#endif
|
||||
|
||||
// nsIWindowWatcher doesn't deal with raw arrays, so we convert the input
|
||||
let params;
|
||||
if (Array.isArray(aParams)) {
|
||||
params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
||||
for (let item of aParams) {
|
||||
let comtaminatedVal;
|
||||
if (item && item instanceof Ci.nsISupports) {
|
||||
comtaminatedVal = item;
|
||||
} else {
|
||||
switch (typeof item) {
|
||||
case "boolean":
|
||||
comtaminatedVal = Cc["@mozilla.org/supports-PRBool;1"].
|
||||
createInstance(Ci.nsISupportsPRBool);
|
||||
comtaminatedVal.data = item;
|
||||
break;
|
||||
case "number":
|
||||
comtaminatedVal = Cc["@mozilla.org/supports-PRUint32;1"].
|
||||
createInstance(Ci.nsISupportsPRUint32);
|
||||
comtaminatedVal.data = item;
|
||||
break;
|
||||
case "string":
|
||||
comtaminatedVal = Cc["@mozilla.org/supports-cstring;1"].
|
||||
createInstance(Ci.nsISupportsCString);
|
||||
comtaminatedVal.data = item;
|
||||
break;
|
||||
|
||||
case "undefined":
|
||||
case "object":
|
||||
if (!item) {
|
||||
comtaminatedVal = null;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error("Unexpected parameter type " + (typeof item) + ": " + item);
|
||||
}
|
||||
}
|
||||
params.appendElement(comtaminatedVal, false);
|
||||
}
|
||||
} else {
|
||||
params = aParams;
|
||||
}
|
||||
|
||||
Services.ww.openWindow(aOpener,
|
||||
"chrome://browser/content/migration/migration.xul",
|
||||
"_blank",
|
||||
features,
|
||||
aParams);
|
||||
params);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -31,7 +31,7 @@ var MigrationWizard = {
|
||||
|
||||
this._wiz = document.documentElement;
|
||||
|
||||
let args = (window.arguments && window.arguments[0]) || [];
|
||||
let args = window.arguments;
|
||||
let entryPointId = args[0] || MigrationUtils.MIGRATION_ENTRYPOINT_UNKNOWN;
|
||||
Services.telemetry.getHistogramById("FX_MIGRATION_ENTRY_POINT").add(entryPointId);
|
||||
|
||||
|
@ -187,6 +187,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
|
||||
"@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "AlertsService",
|
||||
"@mozilla.org/alerts-service;1", "nsIAlertsService");
|
||||
|
||||
const ABOUT_NEWTAB = "about:newtab";
|
||||
|
||||
const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
|
||||
@ -1564,16 +1567,6 @@ BrowserGlue.prototype = {
|
||||
if (actions.indexOf("showAlert") == -1)
|
||||
return;
|
||||
|
||||
let notifier;
|
||||
try {
|
||||
notifier = Cc["@mozilla.org/alerts-service;1"].
|
||||
getService(Ci.nsIAlertsService);
|
||||
}
|
||||
catch (e) {
|
||||
// nsIAlertsService is not available for this platform
|
||||
return;
|
||||
}
|
||||
|
||||
let title = getNotifyString({propName: "alertTitle",
|
||||
stringName: "puAlertTitle",
|
||||
stringParams: [appName]});
|
||||
@ -1594,10 +1587,11 @@ BrowserGlue.prototype = {
|
||||
try {
|
||||
// This will throw NS_ERROR_NOT_AVAILABLE if the notification cannot
|
||||
// be displayed per the idl.
|
||||
notifier.showAlertNotification(null, title, text,
|
||||
true, url, clickCallback);
|
||||
AlertsService.showAlertNotification(null, title, text,
|
||||
true, url, clickCallback);
|
||||
}
|
||||
catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
},
|
||||
|
||||
@ -1877,7 +1871,7 @@ BrowserGlue.prototype = {
|
||||
},
|
||||
|
||||
_migrateUI: function BG__migrateUI() {
|
||||
const UI_VERSION = 31;
|
||||
const UI_VERSION = 32;
|
||||
const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
|
||||
let currentUIVersion = 0;
|
||||
try {
|
||||
@ -2218,10 +2212,49 @@ BrowserGlue.prototype = {
|
||||
xulStore.removeValue(BROWSER_DOCURL, "home-button", "class");
|
||||
}
|
||||
|
||||
if (currentUIVersion < 32) {
|
||||
this._notifyNotificationsUpgrade();
|
||||
}
|
||||
|
||||
// Update the migration version.
|
||||
Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
|
||||
},
|
||||
|
||||
_hasExistingNotificationPermission: function BG__hasExistingNotificationPermission() {
|
||||
let enumerator = Services.perms.enumerator;
|
||||
while (enumerator.hasMoreElements()) {
|
||||
let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
|
||||
if (permission.type == "desktop-notification") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_notifyNotificationsUpgrade: function BG__notifyNotificationsUpgrade() {
|
||||
if (!this._hasExistingNotificationPermission()) {
|
||||
return;
|
||||
}
|
||||
function clickCallback(subject, topic, data) {
|
||||
if (topic != "alertclickcallback")
|
||||
return;
|
||||
let win = RecentWindow.getMostRecentBrowserWindow();
|
||||
win.openUILinkIn(data, "tab");
|
||||
}
|
||||
let imageURL = "chrome://browser/skin/web-notifications-icon.svg";
|
||||
let title = gBrowserBundle.GetStringFromName("webNotifications.upgradeTitle");
|
||||
let text = gBrowserBundle.GetStringFromName("webNotifications.upgradeInfo");
|
||||
let url = Services.urlFormatter.formatURLPref("browser.push.warning.infoURL");
|
||||
|
||||
try {
|
||||
AlertsService.showAlertNotification(imageURL, title, text,
|
||||
true, url, clickCallback);
|
||||
}
|
||||
catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
},
|
||||
|
||||
// ------------------------------
|
||||
// public nsIBrowserGlue members
|
||||
// ------------------------------
|
||||
|
@ -74,6 +74,10 @@ var gContentPane = {
|
||||
setEventListener("notificationsDoNotDisturb", "command",
|
||||
gContentPane.toggleDoNotDisturbNotifications);
|
||||
|
||||
let notificationInfoURL = Services.urlFormatter.formatURLPref("browser.push.warning.infoURL");
|
||||
document.getElementById("notificationsPolicyLearnMore").setAttribute("href",
|
||||
notificationInfoURL);
|
||||
|
||||
let drmInfoURL =
|
||||
Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
|
||||
document.getElementById("playDRMContentLink").setAttribute("href", drmInfoURL);
|
||||
|
@ -68,10 +68,13 @@
|
||||
<column/>
|
||||
</columns>
|
||||
<rows>
|
||||
<row id="notificationsPolicyRow">
|
||||
<vbox align="start">
|
||||
<row id="notificationsPolicyRow" align="center">
|
||||
<hbox align="start">
|
||||
<label id="notificationsPolicy">¬ificationsPolicyDesc2.label;</label>
|
||||
</vbox>
|
||||
<label id="notificationsPolicyLearnMore"
|
||||
class="text-link"
|
||||
value="¬ificationsPolicyLearnMore.label;"/>
|
||||
</hbox>
|
||||
<hbox pack="end">
|
||||
<button id="notificationsPolicyButton" label="¬ificationsPolicyButton.label;"
|
||||
accesskey="¬ificationsPolicyButton.accesskey;"/>
|
||||
|
@ -212,12 +212,15 @@
|
||||
</hbox>
|
||||
<label class="fxaMobilePromo">
|
||||
&mobilePromo.start;<!-- We put these comments to avoid inserting white spaces
|
||||
--><image class="androidLogo"/><!--
|
||||
--><label class="androidLink text-link" href="https://www.mozilla.org/firefox/android/"><!--
|
||||
--><label class="androidLink text-link"
|
||||
href="https://www.mozilla.org/firefox/android/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=sync-preferences"><!--
|
||||
-->&mobilePromo.androidLink;</label><!--
|
||||
-->&mobilePromo.iOSBefore;<!--
|
||||
--><label class="iOSLink text-link"
|
||||
href="https://www.mozilla.org/firefox/ios/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=sync-preferences"><!--
|
||||
-->&mobilePromo.iOSLink;</label><!--
|
||||
-->&mobilePromo.end;
|
||||
</label>
|
||||
<label class="androidAttribution">&androidAttribution;</label>
|
||||
</vbox>
|
||||
|
||||
<vbox id="hasFxaAccount">
|
||||
@ -342,15 +345,18 @@
|
||||
</hbox>
|
||||
</hbox>
|
||||
</groupbox>
|
||||
<spacer class="separator"/>
|
||||
<label class="fxaMobilePromo">
|
||||
&mobilePromo.start;<!-- We put these comments to avoid inserting white spaces
|
||||
--><image class="androidLogo"/><!--
|
||||
--><label class="androidLink text-link" href="https://www.mozilla.org/firefox/android/"><!--
|
||||
--><label class="androidLink text-link"
|
||||
href="https://www.mozilla.org/firefox/android/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=sync-preferences"><!--
|
||||
-->&mobilePromo.androidLink;</label><!--
|
||||
-->&mobilePromo.iOSBefore;<!--
|
||||
--><label class="iOSLink text-link"
|
||||
href="https://www.mozilla.org/firefox/ios/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=sync-preferences"><!--
|
||||
-->&mobilePromo.iOSLink;</label><!--
|
||||
-->&mobilePromo.end;
|
||||
</label>
|
||||
<spacer flex="1"/>
|
||||
<spacer class="separator" flex="1"/>
|
||||
<vbox id="tosPP-small" align="start">
|
||||
<label id="tosPP-small-ToS" class="text-link">
|
||||
&prefs.tosLink.label;
|
||||
@ -359,6 +365,5 @@
|
||||
&fxaPrivacyNotice.link.label;
|
||||
</label>
|
||||
</vbox>
|
||||
<label class="androidAttribution">&androidAttribution;</label>
|
||||
</vbox>
|
||||
</deck>
|
||||
|
@ -10,23 +10,9 @@
|
||||
a replacement for the standard security certificate errors produced
|
||||
by NSS/PSM via netError.xhtml. -->
|
||||
|
||||
<!ENTITY certerror.pagetitle "Untrusted Connection">
|
||||
<!ENTITY certerror.longpagetitle "This Connection is Untrusted">
|
||||
|
||||
<!-- These are going to be used for the updated design in Bug 1207107 -->
|
||||
<!ENTITY certerror.pagetitle1 "Insecure Connection">
|
||||
<!ENTITY certerror.longpagetitle1 "Your connection is not secure">
|
||||
|
||||
<!-- Localization note (certerror.introPara1) - The string "#1" will
|
||||
be replaced at runtime with the name of the server to which the user
|
||||
was trying to connect. -->
|
||||
<!ENTITY certerror.introPara1 "You have asked &brandShortName; to connect
|
||||
securely to <b>#1</b>, but we can't confirm that your connection is secure.">
|
||||
<!ENTITY certerror.introPara2 "Normally, when you try to connect securely,
|
||||
sites will present trusted identification to prove that you are
|
||||
going to the right place. However, this site's identity can't be verified.">
|
||||
|
||||
<!-- These are going to be used for the udpated design in Bug 1207107 -->
|
||||
<!-- Localization note (certerror.introPara) - The text content of the span tag
|
||||
will be replaced at runtime with the name of the server to which the user
|
||||
was trying to connect. -->
|
||||
@ -35,17 +21,11 @@ was trying to connect. -->
|
||||
<!ENTITY certerror.learnMore "Learn more…">
|
||||
<!ENTITY certerror.advanced.label "Advanced">
|
||||
|
||||
<!ENTITY certerror.whatShouldIDo.heading "What Should I Do?">
|
||||
<!ENTITY certerror.whatShouldIDo.content "If you usually connect to
|
||||
this site without problems, this error could mean that someone is
|
||||
trying to impersonate the site, and you shouldn't continue.">
|
||||
<!ENTITY certerror.whatShouldIDo.badStsCertExplanation "This site uses HTTP
|
||||
Strict Transport Security (HSTS) to specify that &brandShortName; only connect
|
||||
to it securely. As a result, it is not possible to add an exception for this
|
||||
certificate.">
|
||||
<!ENTITY certerror.getMeOutOfHere.label "Get me out of here!">
|
||||
|
||||
<!ENTITY certerror.expert.heading "I Understand the Risks">
|
||||
<!ENTITY certerror.expert.content "If you understand what's going on, you
|
||||
can tell &brandShortName; to start trusting this site's identification.
|
||||
<b>Even if you trust the site, this error could mean that someone is
|
||||
@ -53,5 +33,3 @@ tampering with your connection.</b>">
|
||||
<!ENTITY certerror.expert.contentPara2 "Don't add an exception unless
|
||||
you know there's a good reason why this site doesn't use trusted identification.">
|
||||
<!ENTITY certerror.addException.label "Add Exception…">
|
||||
|
||||
<!ENTITY certerror.technical.heading "Technical Details">
|
||||
|
@ -382,6 +382,8 @@ webNotifications.alwaysReceive.accesskey=A
|
||||
webNotifications.neverShow=Always Block Notifications
|
||||
webNotifications.neverShow.accesskey=N
|
||||
webNotifications.receiveFromSite=Would you like to receive notifications from this site?
|
||||
webNotifications.upgradeTitle=Upgraded notifications
|
||||
webNotifications.upgradeInfo=You will receive notifications from sites, even those not open in a tab. Click to learn more.
|
||||
|
||||
# Pointer lock UI
|
||||
|
||||
|
@ -61,6 +61,9 @@ tree-item.nostack=(no stack available)
|
||||
# that represents the root of the tree when inverted.
|
||||
tree-item.root=(root)
|
||||
|
||||
# LOCALIZATION NOTE (tree-item.percent): A percent of bytes or count displayed in the tree view.
|
||||
tree-item.percent=%S%
|
||||
|
||||
# LOCALIZATION NOTE (snapshot.state.saving.full): The label describing the snapshot
|
||||
# state SAVING, used in the main heap view.
|
||||
snapshot.state.saving.full=Saving snapshot…
|
||||
@ -114,3 +117,8 @@ heapview.field.totalcount=Total Count
|
||||
|
||||
# LOCALIZATION NOTE (heapview.field.name): The name of the column in the heap view for name.
|
||||
heapview.field.name=Name
|
||||
|
||||
# LOCALIZATION NOTE (unknownSource): When we do not know the source filename of
|
||||
# a frame in the allocation stack breakdown tree view, we use this string
|
||||
# instead.
|
||||
unknownSource=(unknown)
|
||||
|
@ -8,7 +8,8 @@
|
||||
<!ENTITY blockPopups.accesskey "B">
|
||||
|
||||
<!ENTITY notificationsPolicy.label "Notifications">
|
||||
<!ENTITY notificationsPolicyDesc2.label "Choose which sites are allowed to receive notifications">
|
||||
<!ENTITY notificationsPolicyLearnMore.label "Learn more">
|
||||
<!ENTITY notificationsPolicyDesc2.label "Choose which sites are allowed to receive notifications">
|
||||
<!ENTITY notificationsPolicyButton.accesskey "h">
|
||||
<!ENTITY notificationsPolicyButton.label "Choose…">
|
||||
<!ENTITY notificationsDoNotDisturb.label "Do not disturb me">
|
||||
|
@ -91,8 +91,18 @@ both, to better adapt this sentence to their language.
|
||||
|
||||
<!ENTITY signedIn.engines.label "Sync across all devices">
|
||||
|
||||
<!ENTITY mobilePromo.start "Download Firefox for ">
|
||||
<!-- LOCALIZATION NOTE (mobilePromo.*): the following strings will be used to
|
||||
create a single sentence with active links.
|
||||
The resulting sentence in English is: "Sync to your mobile device.
|
||||
Download Firefox for Android or Firefox for iOS." -->
|
||||
|
||||
<!ENTITY mobilePromo.start "Sync to your mobile device. Download ">
|
||||
<!-- LOCALIZATION NOTE (mobilePromo.androidLink): This is a link title that links to https://www.mozilla.org/firefox/android/ -->
|
||||
<!ENTITY mobilePromo.androidLink "Android™">
|
||||
<!ENTITY mobilePromo.end " to sync with your mobile device.">
|
||||
<!ENTITY androidAttribution "Android is a trademark of Google Inc.">
|
||||
<!ENTITY mobilePromo.androidLink "Firefox for Android">
|
||||
|
||||
<!-- LOCALIZATION NOTE (mobilePromo.iOSBefore): This is text displayed between mobilePromo.androidLink and mobilePromo.iosLink -->
|
||||
<!ENTITY mobilePromo.iOSBefore " or ">
|
||||
<!-- LOCALIZATION NOTE (mobilePromo.iOSLink): This is a link title that links to https://www.mozilla.org/firefox/ios/ -->
|
||||
<!ENTITY mobilePromo.iOSLink "Firefox for iOS">
|
||||
|
||||
<!ENTITY mobilePromo.end ".">
|
||||
|
@ -2,71 +2,97 @@
|
||||
* 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 {
|
||||
background: -moz-Dialog;
|
||||
}
|
||||
@import url("chrome://global/skin/in-content/common.css");
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0 1em;
|
||||
color: -moz-FieldText;
|
||||
font: message-box;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 0 .6em 0;
|
||||
border-bottom: 1px solid ThreeDLightShadow;
|
||||
font-size: 160%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 130%;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
min-height: 100vh;
|
||||
padding: 0 48px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#errorPageContainer {
|
||||
position: relative;
|
||||
min-width: 13em;
|
||||
max-width: 52em;
|
||||
margin: 4em auto;
|
||||
border: 1px solid #FFBD09; /* pale yellow extracted from yellow passport icon */
|
||||
border-radius: 10px;
|
||||
padding: 3em;
|
||||
-moz-padding-start: 30px;
|
||||
background: url("chrome://global/skin/icons/sslWarning.png") left 0 no-repeat -moz-Field;
|
||||
background-origin: content-box;
|
||||
}
|
||||
|
||||
#errorPageContainer:-moz-dir(rtl) {
|
||||
background-position: right 0;
|
||||
min-width: 320px;
|
||||
max-width: 512px;
|
||||
}
|
||||
|
||||
#errorTitle {
|
||||
-moz-margin-start: 80px;
|
||||
background: url("chrome://browser/skin/cert-error.svg") left 0 no-repeat;
|
||||
background-size: 3em;
|
||||
margin-inline-start: -5em;
|
||||
padding-inline-start: 5em;
|
||||
}
|
||||
|
||||
#errorLongContent {
|
||||
-moz-margin-start: 80px;
|
||||
#errorTitle:-moz-dir(rtl) {
|
||||
background-position: right 0;
|
||||
}
|
||||
|
||||
.expander > button {
|
||||
-moz-padding-start: 20px;
|
||||
-moz-margin-start: -20px;
|
||||
background: url("chrome://browser/skin/aboutCertError_sectionExpanded.png") left center no-repeat;
|
||||
border: none;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
#errorTitleText {
|
||||
border-bottom: 1px solid #C1C1C1;
|
||||
padding-bottom: 0.4em;
|
||||
}
|
||||
|
||||
@media (max-width: 675px) {
|
||||
#errorTitle {
|
||||
padding-top: 0;
|
||||
background-image: none;
|
||||
margin-inline-start: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#buttonContainer {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
#buttonSpacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Pressing the retry button will cause the cursor to flicker from a pointer to
|
||||
* not-allowed. Override the disabled cursor behaviour since we will never show
|
||||
* the button disabled as the initial state. Remove this in Bug 1219861. */
|
||||
button:disabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.expander > button:-moz-dir(rtl) {
|
||||
background-position: right center;
|
||||
#returnButton {
|
||||
background-color: var(--in-content-primary-button-background);
|
||||
border: none;
|
||||
color: var(--in-content-selected-text);
|
||||
min-width: 250px;
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
.expander[collapsed] > button {
|
||||
background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed.png");
|
||||
#returnButton:hover {
|
||||
background-color: var(--in-content-primary-button-background-hover) !important;
|
||||
}
|
||||
|
||||
.expander[collapsed] > button:-moz-dir(rtl) {
|
||||
background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed-rtl.png");
|
||||
#returnButton:hover:active {
|
||||
background-color: var(--in-content-primary-button-background-active) !important;
|
||||
}
|
||||
|
||||
#advancedButton {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
/* Advanced section is hidden via inline styles until the link is clicked */
|
||||
#advancedPanel {
|
||||
background-color: white;
|
||||
color: var(--in-content-text-color);
|
||||
border: 1px lightgray solid;
|
||||
/* Don't use top padding because the default p style has top padding, and it
|
||||
* makes the overall div look uneven */
|
||||
padding: 0 12px 10px;
|
||||
margin-top: 10px;
|
||||
box-shadow: 0 0 4px #ddd;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.hostname {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
43
browser/themes/shared/incontent-icons/cert-error.svg
Normal file
43
browser/themes/shared/incontent-icons/cert-error.svg
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="45"
|
||||
height="45"
|
||||
viewBox="0 0 45 45">
|
||||
|
||||
<style>
|
||||
.icon-default {
|
||||
fill: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
<defs>
|
||||
<rect id="shape-lock-clasp-outer" x="8" y="2" width="28" height="40" rx="14" ry="14" />
|
||||
<rect id="shape-lock-clasp-inner" x="14" y="8" width="16" height="28" rx="8" ry="8" />
|
||||
<rect id="shape-lock-base" x="4" y="18" width="36" height="24" rx="3" ry="3" />
|
||||
|
||||
<mask id="mask-clasp-cutout">
|
||||
<rect width="48" height="48" fill="#000" />
|
||||
<use xlink:href="#shape-lock-clasp-outer" fill="#fff" />
|
||||
<use xlink:href="#shape-lock-clasp-inner" fill="#000" />
|
||||
<line x1="4" y1="38" x2="41" y2="3" stroke="#000" stroke-width="5.5" />
|
||||
<line x1="4" y1="46" x2="41" y2="11" stroke="#000" stroke-width="5.5" />
|
||||
<rect x="4" y="18" width="36" height="26" rx="6" ry="6" />
|
||||
</mask>
|
||||
|
||||
<mask id="mask-base-cutout">
|
||||
<rect width="45" height="45" fill="#000" />
|
||||
<use xlink:href="#shape-lock-base" fill="#fff" />
|
||||
<line x1="2.5" y1="41.5" x2="41" y2="5" stroke="#000" stroke-width="8.5" />
|
||||
</mask>
|
||||
</defs>
|
||||
|
||||
<use xlink:href="#shape-lock-clasp-outer" mask="url(#mask-clasp-cutout)" fill="#999" />
|
||||
<use xlink:href="#shape-lock-base" mask="url(#mask-base-cutout)" fill="#999" />
|
||||
|
||||
<line x1="2.5" y1="41.5" x2="41" y2="5" stroke="#d92d21" stroke-width="5.5" />
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
@ -171,6 +171,10 @@ treecol {
|
||||
margin-right: 4px !important;
|
||||
}
|
||||
|
||||
#notificationsPolicyLearnMore {
|
||||
-moz-margin-start: 1.5em !important;
|
||||
}
|
||||
|
||||
#defaultFontSizeLabel {
|
||||
/* !important needed to override common !important rule */
|
||||
-moz-margin-start: 4px !important;
|
||||
@ -620,8 +624,7 @@ description > html|a {
|
||||
}
|
||||
|
||||
.fxaMobilePromo {
|
||||
margin-top: 14px;
|
||||
margin-bottom: 41px;
|
||||
margin-bottom: 31px;
|
||||
}
|
||||
|
||||
#fxaLoginRejectedWarning {
|
||||
@ -634,28 +637,16 @@ description > html|a {
|
||||
margin-bottom: 27.5px;
|
||||
}
|
||||
|
||||
.androidLogo {
|
||||
list-style-image: url(chrome://browser/skin/fxa/android.png);
|
||||
max-width: 24px;
|
||||
position: relative;
|
||||
top: 8px;
|
||||
margin: 0px;
|
||||
margin-inline-end: 5px;
|
||||
}
|
||||
|
||||
.androidLink {
|
||||
.androidLink,
|
||||
.iOSLink {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#tosPP-small {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.androidAttribution {
|
||||
font-size: 12px;
|
||||
color: #D1D2D3;
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
.fxaSyncIllustration {
|
||||
list-style-image: url(chrome://browser/skin/fxa/sync-illustration@2x.png)
|
||||
@ -663,9 +654,6 @@ description > html|a {
|
||||
.fxaFirefoxLogo {
|
||||
list-style-image: url(chrome://browser/skin/fxa/logo@2x.png);
|
||||
}
|
||||
.androidLogo {
|
||||
list-style-image: url(chrome://browser/skin/fxa/android@2x.png);
|
||||
}
|
||||
#fxaProfileImage {
|
||||
list-style-image: url(chrome://browser/skin/fxa/default-avatar@2x.png);
|
||||
}
|
||||
|
@ -114,6 +114,7 @@
|
||||
skin/classic/browser/urlbar-arrow.png (../shared/urlbar-arrow.png)
|
||||
skin/classic/browser/urlbar-arrow@2x.png (../shared/urlbar-arrow@2x.png)
|
||||
skin/classic/browser/warning.svg (../shared/warning.svg)
|
||||
skin/classic/browser/cert-error.svg (../shared/incontent-icons/cert-error.svg)
|
||||
skin/classic/browser/session-restore.svg (../shared/incontent-icons/session-restore.svg)
|
||||
skin/classic/browser/tab-crashed.svg (../shared/incontent-icons/tab-crashed.svg)
|
||||
skin/classic/browser/welcome-back.svg (../shared/incontent-icons/welcome-back.svg)
|
||||
|
@ -12,6 +12,12 @@
|
||||
// rate changed have a delay = delay/rate and a duration = duration/rate.
|
||||
|
||||
add_task(function*() {
|
||||
yield new Promise(resolve => {
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
["dom.animations-api.core.enabled", true]
|
||||
]}, resolve);
|
||||
});
|
||||
|
||||
yield addTab(TEST_URL_ROOT + "doc_modify_playbackRate.html");
|
||||
|
||||
let {panel} = yield openAnimationInspector();
|
||||
|
@ -3,8 +3,7 @@
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
|
||||
const { L10N } = require("../utils");
|
||||
const { URL } = require("sdk/url");
|
||||
const { L10N, parseSource } = require("../utils");
|
||||
|
||||
const Frame = module.exports = createClass({
|
||||
displayName: "frame-view",
|
||||
@ -16,25 +15,24 @@ const Frame = module.exports = createClass({
|
||||
|
||||
render() {
|
||||
let { toolbox, frame } = this.props;
|
||||
const { short, long, host } = parseSource(frame.source);
|
||||
|
||||
let url = new URL(frame.source);
|
||||
let spec = url.toString();
|
||||
let func = frame.functionDisplayName || "";
|
||||
let tooltip = `${func} (${spec}:${frame.line}:${frame.column})`;
|
||||
let viewTooltip = L10N.getFormatStr("viewsourceindebugger", `${spec}:${frame.line}:${frame.column}`);
|
||||
let onClick = () => toolbox.viewSourceInDebugger(spec, frame.line);
|
||||
let tooltip = `${func} (${long}:${frame.line}:${frame.column})`;
|
||||
let viewTooltip = L10N.getFormatStr("viewsourceindebugger", `${long}:${frame.line}:${frame.column}`);
|
||||
let onClick = () => toolbox.viewSourceInDebugger(long, frame.line);
|
||||
|
||||
let fields = [
|
||||
dom.span({ className: "frame-link-function-display-name" }, func),
|
||||
dom.a({ className: "frame-link-filename", onClick, title: viewTooltip }, url.fileName),
|
||||
dom.a({ className: "frame-link-filename", onClick, title: viewTooltip }, short),
|
||||
dom.span({ className: "frame-link-colon" }, ":"),
|
||||
dom.span({ className: "frame-link-line" }, frame.line),
|
||||
dom.span({ className: "frame-link-colon" }, ":"),
|
||||
dom.span({ className: "frame-link-column" }, frame.column)
|
||||
];
|
||||
|
||||
if (url.scheme === "http" || url.scheme === "https" || url.scheme === "ftp") {
|
||||
fields.push(dom.span({ className: "frame-link-host" }, url.host));
|
||||
if (host) {
|
||||
fields.push(dom.span({ className: "frame-link-host" }, host));
|
||||
}
|
||||
|
||||
return dom.span({ className: "frame-link", title: tooltip }, ...fields);
|
||||
|
@ -6,7 +6,7 @@ const { DOM: dom, createClass, PropTypes, createFactory } = require("devtools/cl
|
||||
const { safeErrorString } = require("devtools/shared/DevToolsUtils");
|
||||
const Tree = createFactory(require("./tree"));
|
||||
const TreeItem = createFactory(require("./tree-item"));
|
||||
const { getSnapshotStatusTextFull, L10N } = require("../utils");
|
||||
const { getSnapshotStatusTextFull, getSnapshotTotals, L10N } = require("../utils");
|
||||
const { snapshotState: states } = require("../constants");
|
||||
const { snapshot: snapshotModel } = require("../models");
|
||||
// If HEAP_TREE_ROW_HEIGHT changes, be sure to change `var(--heap-tree-row-height)`
|
||||
@ -36,13 +36,27 @@ function createParentMap (node, aggregator=Object.create(null)) {
|
||||
* @param {CensusTreeNode} census
|
||||
* @return {Object}
|
||||
*/
|
||||
function createTreeProperties (census, toolbox) {
|
||||
function createTreeProperties (snapshot, toolbox) {
|
||||
const census = snapshot.census;
|
||||
let map = createParentMap(census);
|
||||
const totals = getSnapshotTotals(snapshot);
|
||||
|
||||
return {
|
||||
getParent: node => map[node.id],
|
||||
getParent: node => {
|
||||
const parent = map[node.id];
|
||||
return parent === census ? null : parent;
|
||||
},
|
||||
getChildren: node => node.children || [],
|
||||
renderItem: (item, depth, focused, arrow) => new TreeItem({ toolbox, item, depth, focused, arrow }),
|
||||
renderItem: (item, depth, focused, arrow) =>
|
||||
new TreeItem({
|
||||
toolbox,
|
||||
item,
|
||||
depth,
|
||||
focused,
|
||||
arrow,
|
||||
getPercentBytes: bytes => bytes / totals.bytes * 100,
|
||||
getPercentCount: count => count / totals.count * 100,
|
||||
}),
|
||||
getRoots: () => census.children,
|
||||
getKey: node => node.id,
|
||||
itemHeight: HEAP_TREE_ROW_HEIGHT,
|
||||
@ -115,7 +129,7 @@ const Heap = module.exports = createClass({
|
||||
dom.span({ className: "heap-tree-item-total-count" }, L10N.getStr("heapview.field.totalcount")),
|
||||
dom.span({ className: "heap-tree-item-name" }, L10N.getStr("heapview.field.name"))
|
||||
),
|
||||
Tree(createTreeProperties(snapshot.census, toolbox))
|
||||
Tree(createTreeProperties(snapshot, toolbox))
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
@ -44,4 +44,3 @@ const SnapshotListItem = module.exports = createClass({
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -40,33 +40,35 @@ const Toolbar = module.exports = createClass({
|
||||
title: L10N.getStr("take-snapshot")
|
||||
}),
|
||||
|
||||
dom.label({},
|
||||
L10N.getStr("toolbar.breakdownBy"),
|
||||
dom.select({
|
||||
id: "select-breakdown",
|
||||
className: `select-breakdown`,
|
||||
onChange: e => onBreakdownChange(e.target.value),
|
||||
}, ...breakdowns.map(({ name, displayName }) => dom.option({ key: name, value: name }, displayName)))
|
||||
),
|
||||
dom.div({ className: "toolbar-group" },
|
||||
dom.label({ className: "breakdown-by" },
|
||||
L10N.getStr("toolbar.breakdownBy"),
|
||||
dom.select({
|
||||
id: "select-breakdown",
|
||||
className: `select-breakdown`,
|
||||
onChange: e => onBreakdownChange(e.target.value),
|
||||
}, ...breakdowns.map(({ name, displayName }) => dom.option({ key: name, value: name }, displayName)))
|
||||
),
|
||||
|
||||
dom.label({},
|
||||
dom.input({
|
||||
id: "invert-tree-checkbox",
|
||||
type: "checkbox",
|
||||
checked: inverted,
|
||||
onChange: onToggleInverted,
|
||||
}),
|
||||
L10N.getStr("checkbox.invertTree")
|
||||
),
|
||||
dom.label({},
|
||||
dom.input({
|
||||
id: "invert-tree-checkbox",
|
||||
type: "checkbox",
|
||||
checked: inverted,
|
||||
onChange: onToggleInverted,
|
||||
}),
|
||||
L10N.getStr("checkbox.invertTree")
|
||||
),
|
||||
|
||||
dom.label({},
|
||||
dom.input({
|
||||
type: "checkbox",
|
||||
checked: allocations.recording,
|
||||
disabled: allocations.togglingInProgress,
|
||||
onChange: onToggleRecordAllocationStacks,
|
||||
}),
|
||||
L10N.getStr("checkbox.recordAllocationStacks")
|
||||
dom.label({},
|
||||
dom.input({
|
||||
type: "checkbox",
|
||||
checked: allocations.recording,
|
||||
disabled: allocations.togglingInProgress,
|
||||
onChange: onToggleRecordAllocationStacks,
|
||||
}),
|
||||
L10N.getStr("checkbox.recordAllocationStacks")
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -18,14 +18,39 @@ const MAX_SOURCE_LENGTH = 200;
|
||||
const TreeItem = module.exports = createClass({
|
||||
displayName: "tree-item",
|
||||
|
||||
formatPercent(percent) {
|
||||
return L10N.getFormatStr("tree-item.percent", Math.round(percent));
|
||||
},
|
||||
|
||||
render() {
|
||||
let { item, depth, arrow, focused, toolbox } = this.props;
|
||||
let {
|
||||
item,
|
||||
depth,
|
||||
arrow,
|
||||
focused,
|
||||
toolbox,
|
||||
getPercentBytes,
|
||||
getPercentCount,
|
||||
} = this.props;
|
||||
|
||||
const percentBytes = this.formatPercent(getPercentBytes(item.bytes));
|
||||
const percentCount = this.formatPercent(getPercentCount(item.count));
|
||||
const percentTotalBytes = this.formatPercent(getPercentBytes(item.totalBytes));
|
||||
const percentTotalCount = this.formatPercent(getPercentBytes(item.totalCount));
|
||||
|
||||
return dom.div({ className: `heap-tree-item ${focused ? "focused" :""}` },
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" }, item.bytes),
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-count" }, item.count),
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-total-bytes" }, item.totalBytes),
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-total-count" }, item.totalCount),
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" },
|
||||
dom.span({ className: "heap-tree-number" }, item.bytes),
|
||||
dom.span({ className: "heap-tree-percent" }, percentBytes)),
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-count" },
|
||||
dom.span({ className: "heap-tree-number" }, item.count),
|
||||
dom.span({ className: "heap-tree-percent" }, percentCount)),
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-total-bytes" },
|
||||
dom.span({ className: "heap-tree-number" }, item.totalBytes),
|
||||
dom.span({ className: "heap-tree-percent" }, percentTotalBytes)),
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-total-count" },
|
||||
dom.span({ className: "heap-tree-number" }, item.totalCount),
|
||||
dom.span({ className: "heap-tree-percent" }, percentTotalCount)),
|
||||
dom.span({ className: "heap-tree-item-field heap-tree-item-name", style: { marginLeft: depth * INDENT }},
|
||||
arrow,
|
||||
this.toLabel(item.name, toolbox)
|
||||
|
@ -51,7 +51,7 @@ const breakdowns = exports.breakdowns = {
|
||||
displayName: "Coarse Type",
|
||||
breakdown: {
|
||||
by: "coarseType",
|
||||
objects: ALLOCATION_STACK,
|
||||
objects: OBJECT_CLASS,
|
||||
strings: COUNT,
|
||||
scripts: INTERNAL_TYPE,
|
||||
other: INTERNAL_TYPE,
|
||||
|
@ -9,6 +9,7 @@ support-files =
|
||||
[browser_memory-breakdowns-01.js]
|
||||
skip-if = debug # bug 1219554
|
||||
[browser_memory_no_allocation_stacks.js]
|
||||
[browser_memory_percents_01.js]
|
||||
[browser_memory-simple-01.js]
|
||||
skip-if = debug # bug 1219554
|
||||
[browser_memory_transferHeapSnapshot_e10s_01.js]
|
||||
|
@ -0,0 +1,48 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Sanity test that we calculate percentages in the tree.
|
||||
|
||||
"use strict";
|
||||
|
||||
const { breakdowns } = require("devtools/client/memory/constants");
|
||||
const { takeSnapshotAndCensus } = require("devtools/client/memory/actions/snapshot");
|
||||
const breakdownActions = require("devtools/client/memory/actions/breakdown");
|
||||
|
||||
const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_steady_allocation.html";
|
||||
|
||||
function checkCells(cells) {
|
||||
ok(cells.length > 1, "Should have found some");
|
||||
// Ignore the first header cell.
|
||||
for (let cell of cells.slice(1)) {
|
||||
const percent = cell.querySelector(".heap-tree-percent");
|
||||
ok(percent, "should have a percent cell");
|
||||
ok(percent.textContent.match(/^\d?\d%$/), "should be of the form nn% or n%");
|
||||
}
|
||||
}
|
||||
|
||||
this.test = makeMemoryTest(TEST_URL, function* ({ tab, panel }) {
|
||||
const heapWorker = panel.panelWin.gHeapAnalysesClient;
|
||||
const front = panel.panelWin.gFront;
|
||||
const { getState, dispatch } = panel.panelWin.gStore;
|
||||
const doc = panel.panelWin.document;
|
||||
|
||||
yield dispatch(takeSnapshotAndCensus(front, heapWorker));
|
||||
yield dispatch(breakdownActions.setBreakdownAndRefresh(heapWorker,
|
||||
breakdowns.objectClass.breakdown));
|
||||
|
||||
is(getState().breakdown.by, "objectClass",
|
||||
"Should be using object class breakdown");
|
||||
|
||||
const bytesCells = [...doc.querySelectorAll(".heap-tree-item-bytes")];
|
||||
checkCells(bytesCells);
|
||||
|
||||
const totalBytesCells = [...doc.querySelectorAll(".heap-tree-item-total-bytes")];
|
||||
checkCells(totalBytesCells);
|
||||
|
||||
const countCells = [...doc.querySelectorAll(".heap-tree-item-count")];
|
||||
checkCells(countCells);
|
||||
|
||||
const totalCountCells = [...doc.querySelectorAll(".heap-tree-item-total-count")];
|
||||
checkCells(totalCountCells);
|
||||
});
|
@ -49,3 +49,22 @@ add_task(function *() {
|
||||
ok(utils.breakdownEquals(utils.getCustomBreakdowns()["My Breakdown"], custom),
|
||||
"utils.getCustomBreakdowns() returns custom breakdowns");
|
||||
});
|
||||
|
||||
// Test `utils.parseSource`.
|
||||
add_task(function* () {
|
||||
const url = "http://example.com/foo/bar/baz.js";
|
||||
let results = utils.parseSource(url);
|
||||
equal(results.short, "baz.js");
|
||||
equal(results.long, url);
|
||||
equal(results.host, "example.com");
|
||||
|
||||
results = utils.parseSource("self-hosted");
|
||||
equal(results.short, "self-hosted");
|
||||
equal(results.long, "self-hosted");
|
||||
equal(results.host, undefined);
|
||||
|
||||
results = utils.parseSource("");
|
||||
equal(typeof results.short, "string");
|
||||
equal(typeof results.long, "string");
|
||||
equal(results.host, undefined);
|
||||
});
|
||||
|
@ -3,9 +3,12 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const { Cu } = require("chrome");
|
||||
|
||||
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
|
||||
const STRINGS_URI = "chrome://browser/locale/devtools/memory.properties"
|
||||
const L10N = exports.L10N = new ViewHelpers.L10N(STRINGS_URI);
|
||||
|
||||
const { URL } = require("sdk/url");
|
||||
const { assert } = require("devtools/shared/DevToolsUtils");
|
||||
const { Preferences } = require("resource://gre/modules/Preferences.jsm");
|
||||
const CUSTOM_BREAKDOWN_PREF = "devtools.memory.custom-breakdowns";
|
||||
@ -257,8 +260,10 @@ exports.getSnapshotTotals = function (snapshot) {
|
||||
census = census.children && census.children[0];
|
||||
}
|
||||
} else {
|
||||
bytes = census.totalBytes;
|
||||
count = census.totalCount;
|
||||
if (census) {
|
||||
bytes = census.totalBytes;
|
||||
count = census.totalCount;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@ -267,3 +272,45 @@ exports.getSnapshotTotals = function (snapshot) {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a source into a short and long name as well as a host name.
|
||||
*
|
||||
* @param {String} source
|
||||
* The source to parse.
|
||||
*
|
||||
* @returns {Object}
|
||||
* An object with the following properties:
|
||||
* - {String} short: A short name for the source.
|
||||
* - {String} long: The full, long name for the source.
|
||||
* - {String?} host: If available, the host name for the source.
|
||||
*/
|
||||
exports.parseSource = function (source) {
|
||||
const sourceStr = source ? String(source) : "";
|
||||
|
||||
let short;
|
||||
let long;
|
||||
let host;
|
||||
|
||||
try {
|
||||
const url = new URL(sourceStr);
|
||||
short = url.fileName;
|
||||
host = url.host;
|
||||
long = url.toString();
|
||||
} catch (e) {
|
||||
// Malformed URI.
|
||||
long = sourceStr;
|
||||
short = sourceStr.slice(0, 100);
|
||||
}
|
||||
|
||||
if (!short) {
|
||||
// Last ditch effort.
|
||||
|
||||
if (!long) {
|
||||
long = L10N.getStr("unknownSource");
|
||||
}
|
||||
|
||||
short = long.slice(0, 100);
|
||||
}
|
||||
|
||||
return { short, long, host };
|
||||
};
|
||||
|
@ -19,51 +19,62 @@
|
||||
--row-hover-background-color: rgba(76,158,217,0.2);
|
||||
}
|
||||
|
||||
html, .theme-body, #app, #memory-tool, #memory-tool-container {
|
||||
html, body, #app, #memory-tool {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.theme-body {
|
||||
overflow: hidden;
|
||||
/* Not sure why .theme-body declares it as `background` and overrides */
|
||||
background-color: var(--theme-toolbar-background) !important;
|
||||
}
|
||||
|
||||
#memory-tool-container {
|
||||
#memory-tool {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
--toolbar-height: 20px;
|
||||
flex-direction: column;
|
||||
--sidebar-width: 185px;
|
||||
/**
|
||||
* If --heap-tree-row-height changes, be sure to change HEAP_TREE_ROW_HEIGHT
|
||||
* in `devtools/client/memory/components/heap.js`.
|
||||
*/
|
||||
--heap-tree-row-height: 14px;
|
||||
--heap-tree-header-height: 17px;
|
||||
}
|
||||
|
||||
#memory-tool .devtools-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.devtools-toolbar .toolbar-group {
|
||||
position: absolute;
|
||||
left: var(--sidebar-width);
|
||||
top: -2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.toolbar-group > label {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.toolbar-group .breakdown-by span {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#memory-tool-container {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO bug 1213100
|
||||
* should generalize toolbar buttons with images in them
|
||||
* toolbars.inc.css contains definitions for .devtools-button,
|
||||
* I wager that many of the below styles can be rolled into that
|
||||
* Toolbar
|
||||
*/
|
||||
|
||||
.devtools-toolbar .devtools-button.take-snapshot {
|
||||
margin: 2px 1px;
|
||||
padding: 1px;
|
||||
border-width: 0px;
|
||||
/* [standalone] buttons override min-height from 18px to 24px -- why? */
|
||||
min-height: 18px;
|
||||
/* not sure why this is needed for positioning */
|
||||
display: -moz-box;
|
||||
-moz-appearance: none;
|
||||
margin-inline-start: 1px;
|
||||
margin-inline-end: 1px;
|
||||
}
|
||||
|
||||
.devtools-toolbar .devtools-button.take-snapshot::before {
|
||||
background-image: url(images/command-screenshot.png);
|
||||
-moz-appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-size: 64px 16px;
|
||||
background-position: 0 center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
.devtools-toolbar .devtools-button.take-snapshot::before {
|
||||
@ -71,17 +82,6 @@ html, .theme-body, #app, #memory-tool, #memory-tool-container {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO bug 1213100
|
||||
* Should this be codified in .devtools-toolbar itself?
|
||||
*/
|
||||
#memory-tool .devtools-toolbar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: var(--toolbar-height);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO bug 1213100
|
||||
* Once we figure out how to store invertable buttons (pseudo element like in this case?)
|
||||
@ -94,30 +94,19 @@ html, .theme-body, #app, #memory-tool, #memory-tool-container {
|
||||
filter: url(images/filters.svg#invert);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO bug 1213100
|
||||
* The .list style is for a generalized React list component. It's children (.list > li)
|
||||
* are generally styled here, as the component can take any type of child component.
|
||||
* Memory tool specific styling are handling in (li.snapshot-list-item).
|
||||
*/
|
||||
|
||||
.list {
|
||||
min-width: var(--sidebar-width);
|
||||
width: var(--sidebar-width);
|
||||
overflow-y: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 186px;
|
||||
list-style-type: none;
|
||||
font-size: 12px;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
background-color: var(--theme-toolbar-background);
|
||||
color: var(--theme-body-color-alt);
|
||||
border-color: var(--theme-splitter-color);
|
||||
border-style: solid;
|
||||
border-width: 0px 1px 0px 0px;
|
||||
background-color: var(--theme-sidebar-background);
|
||||
border-inline-end: 1px solid var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
.list > li {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--theme-body-color);
|
||||
border-bottom: 1px solid rgba(128,128,128,0.15);
|
||||
padding: 8px;
|
||||
@ -129,27 +118,19 @@ html, .theme-body, #app, #memory-tool, #memory-tool-container {
|
||||
color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
.snapshot-list-item {
|
||||
position: relative;
|
||||
}
|
||||
.snapshot-list-item span {
|
||||
.snapshot-list-item .snapshot-title {
|
||||
display: block;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.snapshot-list-item .snapshot-state, .snapshot-list-item .snapshot-totals {
|
||||
|
||||
.snapshot-list-item .snapshot-state,
|
||||
.snapshot-list-item .snapshot-totals {
|
||||
font-size: 90%;
|
||||
color: var(--theme-body-color-alt);
|
||||
position: absolute;
|
||||
}
|
||||
.snapshot-list-item .snapshot-state {
|
||||
top: 38px;
|
||||
}
|
||||
.snapshot-list-item .snapshot-totals {
|
||||
top: 38px;
|
||||
}
|
||||
.snapshot-list-item .total-bytes {
|
||||
float: left;
|
||||
}
|
||||
.snapshot-list-item.selected .snapshot-state, .snapshot-list-item.selected .snapshot-totals {
|
||||
|
||||
.snapshot-list-item.selected .snapshot-state,
|
||||
.snapshot-list-item.selected .snapshot-totals {
|
||||
/* Text inside a selected item should not be custom colored. */
|
||||
color: inherit !important;
|
||||
}
|
||||
@ -159,26 +140,22 @@ html, .theme-body, #app, #memory-tool, #memory-tool-container {
|
||||
*/
|
||||
|
||||
#heap-view {
|
||||
flex: 1 1 auto;
|
||||
border-color: var(--theme-splitter-color);
|
||||
color: var(--theme-body-color);
|
||||
border-left-width: 1px;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
background-color: var(--theme-toolbar-background);
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
#heap-view .heap-view-panel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--theme-toolbar-background)
|
||||
}
|
||||
|
||||
#heap-view .heap-view-panel .snapshot-status, #heap-view .take-snapshot {
|
||||
#heap-view .snapshot-status,
|
||||
#heap-view .take-snapshot {
|
||||
margin: auto;
|
||||
margin-top: 65px;
|
||||
font-size: 120%;
|
||||
display: block;
|
||||
margin: 65px auto;
|
||||
}
|
||||
|
||||
#heap-view .heap-view-panel .snapshot-status {
|
||||
width: 500px;
|
||||
#heap-view .snapshot-status {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -186,10 +163,16 @@ html, .theme-body, #app, #memory-tool, #memory-tool-container {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#heap-view .heap-view-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#heap-view .heap-view-panel[data-state="snapshot-state-error"] pre {
|
||||
/* TODO */
|
||||
background-color: var(--theme-body-background);
|
||||
overflow-y: scroll;
|
||||
height: 100px;
|
||||
overflow-y: auto;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
@ -202,35 +185,48 @@ html, .theme-body, #app, #memory-tool, #memory-tool-container {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.tree {
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
background-color: var(--theme-body-background);
|
||||
}
|
||||
|
||||
.tree .header {
|
||||
height: 17px;
|
||||
overflow: hidden;
|
||||
.header {
|
||||
height: var(--heap-tree-header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--theme-body-color);
|
||||
background-color: var(--theme-tab-toolbar-background);
|
||||
}
|
||||
|
||||
.tree span {
|
||||
border-left-color: var(--cell-border-color);
|
||||
border-left-width: 1px;
|
||||
line-height: var(--heap-tree-row-height);
|
||||
}
|
||||
|
||||
.tree {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background-color: var(--theme-body-background);
|
||||
}
|
||||
|
||||
.tree-node {
|
||||
height: var(--heap-tree-row-height);
|
||||
clear: left;
|
||||
}
|
||||
|
||||
.heap-tree-percent {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.heap-tree-number {
|
||||
width: 70%;
|
||||
color: var(--theme-content-color3);
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.focused .heap-tree-number {
|
||||
color: var(--theme-selection-color);
|
||||
}
|
||||
|
||||
.heap-tree-item, .header {
|
||||
list-style-type: none;
|
||||
height: var(--heap-tree-row-height);
|
||||
}
|
||||
|
||||
.heap-tree-item-field, .header span {
|
||||
float: left;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.tree-node:nth-child(2n) {
|
||||
@ -248,52 +244,69 @@ html, .theme-body, #app, #memory-tool, #memory-tool-container {
|
||||
|
||||
.header {
|
||||
background-color: var(--theme-tab-toolbar-background);
|
||||
border-color: var(--cell-border-color);
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 1px 0px;
|
||||
}
|
||||
|
||||
.header span {
|
||||
text-align: center;
|
||||
line-height: var(--heap-tree-header-height);
|
||||
font-size: 90%;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.header span, .heap-tree-number, .heap-tree-percent, .heap-tree-item-name {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.header .heap-tree-item-name {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.heap-tree-item-bytes,
|
||||
.heap-tree-item-count,
|
||||
.heap-tree-item-total-bytes,
|
||||
.heap-tree-item-total-count {
|
||||
text-align: right;
|
||||
border-right: var(--cell-border-color) 1px solid;
|
||||
padding-right: 5px;
|
||||
text-align: end;
|
||||
border-inline-end: var(--cell-border-color) 1px solid;
|
||||
padding-inline-end: 5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.heap-tree-item-count,
|
||||
.heap-tree-item-total-count {
|
||||
width: 5vw;
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
.heap-tree-item-bytes,
|
||||
.heap-tree-item-total-bytes {
|
||||
width: 7vw;
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.heap-tree-item-name {
|
||||
width: 50%;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.error::before {
|
||||
content: "";
|
||||
background-image: url(chrome://devtools/skin/themes/images/webconsole.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 72px 60px;
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
display: inline-block;
|
||||
|
||||
background-image: url(chrome://devtools/skin/themes/images/webconsole.svg);
|
||||
background-size: 72px 60px;
|
||||
background-position: -24px -24px;
|
||||
margin: 2px 5px 0 0;
|
||||
background-repeat: no-repeat;
|
||||
margin: 0px;
|
||||
margin-top: 2px;
|
||||
margin-inline-end: 5px;
|
||||
max-height: 12px;
|
||||
}
|
||||
|
||||
.theme-light .error::before {
|
||||
background-image: url(chrome://devtools/skin/themes/images/webconsole.svg#light-icons);
|
||||
}
|
||||
@ -340,3 +353,7 @@ html, .theme-body, #app, #memory-tool, #memory-tool-container {
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
label select {
|
||||
margin: 5px;
|
||||
}
|
||||
|
@ -1364,28 +1364,6 @@ public class BrowserApp extends GeckoApp
|
||||
});
|
||||
}
|
||||
|
||||
private void shareCurrentUrl() {
|
||||
Tab tab = Tabs.getInstance().getSelectedTab();
|
||||
if (tab == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String url = tab.getURL();
|
||||
if (url == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (AboutPages.isAboutReader(url)) {
|
||||
url = ReaderModeUtils.getUrlFromAboutReader(url);
|
||||
}
|
||||
|
||||
GeckoAppShell.openUriExternal(url, "text/plain", "", "",
|
||||
Intent.ACTION_SEND, tab.getDisplayTitle(), false);
|
||||
|
||||
// Context: Sharing via chrome list (no explicit session is active)
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST);
|
||||
}
|
||||
|
||||
private void setToolbarMargin(int margin) {
|
||||
((RelativeLayout.LayoutParams) mGeckoLayout.getLayoutParams()).topMargin = margin;
|
||||
mGeckoLayout.requestLayout();
|
||||
@ -3248,7 +3226,20 @@ public class BrowserApp extends GeckoApp
|
||||
}
|
||||
|
||||
if (itemId == R.id.share) {
|
||||
shareCurrentUrl();
|
||||
tab = Tabs.getInstance().getSelectedTab();
|
||||
if (tab != null) {
|
||||
String url = tab.getURL();
|
||||
if (url != null) {
|
||||
if (AboutPages.isAboutReader(url)) {
|
||||
url = ReaderModeUtils.getUrlFromAboutReader(url);
|
||||
}
|
||||
|
||||
// Context: Sharing via chrome list (no explicit session is active)
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "menu");
|
||||
|
||||
GeckoAppShell.openUriExternal(url, "text/plain", "", "", Intent.ACTION_SEND, tab.getDisplayTitle(), false);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -646,7 +646,7 @@ public abstract class GeckoApp
|
||||
GeckoAppShell.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, title, false);
|
||||
|
||||
// Context: Sharing via chrome list (no explicit session is active)
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST);
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "text");
|
||||
|
||||
} else if ("Snackbar:Show".equals(event)) {
|
||||
final String msg = message.getString("message");
|
||||
|
@ -20,6 +20,7 @@ import org.json.JSONObject;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
@ -52,8 +53,6 @@ public final class IntentHelper implements GeckoEventListener,
|
||||
private static String EXTRA_BROWSER_FALLBACK_URL = "browser_fallback_url";
|
||||
|
||||
/** A partial URI to an error page - the encoded error URI should be appended before loading. */
|
||||
private static final String GENERIC_URI_PREFIX = "about:neterror?e=generic&u=";
|
||||
private static final String MALFORMED_URI_PREFIX = "about:neterror?e=malformedURI&u=";
|
||||
private static String UNKNOWN_PROTOCOL_URI_PREFIX = "about:neterror?e=unknownProtocolFound&u=";
|
||||
|
||||
private static IntentHelper instance;
|
||||
@ -186,24 +185,10 @@ public final class IntentHelper implements GeckoEventListener,
|
||||
|
||||
// For this flow, we follow Chrome's lead:
|
||||
// https://developer.chrome.com/multidevice/android/intents
|
||||
if (intent.hasExtra(EXTRA_BROWSER_FALLBACK_URL)) {
|
||||
final String fallbackUrl = intent.getStringExtra(EXTRA_BROWSER_FALLBACK_URL);
|
||||
String urlToLoad;
|
||||
try {
|
||||
final String anyCaseScheme = new URI(fallbackUrl).getScheme();
|
||||
final String scheme = (anyCaseScheme == null) ? null : anyCaseScheme.toLowerCase(Locale.US);
|
||||
if ("http".equals(scheme) || "https".equals(scheme)) {
|
||||
urlToLoad = fallbackUrl;
|
||||
} else {
|
||||
Log.w(LOGTAG, "Fallback URI uses unsupported scheme: " + scheme);
|
||||
urlToLoad = GENERIC_URI_PREFIX + fallbackUrl;
|
||||
}
|
||||
} catch (final URISyntaxException e) {
|
||||
// Do not include Exception to avoid leaking uris.
|
||||
Log.w(LOGTAG, "Exception parsing fallback URI");
|
||||
urlToLoad = MALFORMED_URI_PREFIX + fallbackUrl;
|
||||
}
|
||||
callback.sendError(urlToLoad);
|
||||
final String fallbackUrl = intent.getStringExtra(EXTRA_BROWSER_FALLBACK_URL);
|
||||
if (isFallbackUrlValid(fallbackUrl)) {
|
||||
// Opens the page in JS.
|
||||
callback.sendError(fallbackUrl);
|
||||
|
||||
} else if (intent.getPackage() != null) {
|
||||
// Note on alternative flows: we could get the intent package from a component, however, for
|
||||
@ -232,6 +217,26 @@ public final class IntentHelper implements GeckoEventListener,
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isFallbackUrlValid(@Nullable final String fallbackUrl) {
|
||||
if (fallbackUrl == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final String anyCaseScheme = new URI(fallbackUrl).getScheme();
|
||||
final String scheme = (anyCaseScheme == null) ? null : anyCaseScheme.toLowerCase(Locale.US);
|
||||
if ("http".equals(scheme) || "https".equals(scheme)) {
|
||||
return true;
|
||||
} else {
|
||||
Log.w(LOGTAG, "Fallback URI uses unsupported scheme: " + scheme + ". Try http or https.");
|
||||
}
|
||||
} catch (final URISyntaxException e) {
|
||||
// Do not include Exception to avoid leaking uris.
|
||||
Log.w(LOGTAG, "URISyntaxException parsing fallback URI");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an about:neterror uri with the unknownProtocolFound text as a parameter.
|
||||
* @param encodedUri The encoded uri. While the page does not open correctly without specifying
|
||||
|
@ -206,7 +206,7 @@ public abstract class HomeFragment extends Fragment {
|
||||
Intent.ACTION_SEND, info.getDisplayTitle(), false);
|
||||
|
||||
// Context: Sharing via chrome homepage contextmenu list (home session should be active)
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST);
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "home_contextmenu");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ public class PromptListAdapter extends ArrayAdapter<PromptListItem> {
|
||||
provider.chooseActivity(p);
|
||||
|
||||
// Context: Sharing via content contextmenu list (no explicit session is active)
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST);
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "promptlist");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
<style name="FxAccountTheme" parent="@style/Gecko" />
|
||||
|
||||
<style name="FxAccountTheme.FxAccountStatusActivity" parent="@FxAccountTheme">
|
||||
<style name="FxAccountTheme.FxAccountStatusActivity" parent="Gecko.Preferences">
|
||||
<item name="android:windowActionBar">true</item>
|
||||
<item name="android:windowNoTitle">false</item>
|
||||
<item name="android:actionBarStyle">@style/ActionBar.FxAccountStatusActivity</item>
|
||||
|
@ -233,7 +233,7 @@ public class GeckoActionProvider {
|
||||
chooseActivity(item.getItemId());
|
||||
|
||||
// Context: Sharing via chrome mainmenu list (no explicit session is active)
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST);
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "actionprovider");
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -243,7 +243,7 @@ public class GeckoActionProvider {
|
||||
chooseActivity(index);
|
||||
|
||||
// Context: Sharing via chrome mainmenu and content contextmenu quickshare (no explicit session is active)
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.BUTTON);
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.BUTTON, "actionprovider");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -721,6 +721,9 @@ var BrowserApp = {
|
||||
NativeWindow.contextmenus.add(stringGetter("contextmenu.addToReadingList"),
|
||||
NativeWindow.contextmenus.linkOpenableContext,
|
||||
function(aTarget) {
|
||||
UITelemetry.addEvent("action.1", "contextmenu", null, "web_reading_list");
|
||||
UITelemetry.addEvent("save.1", "contextmenu", null, "reading_list");
|
||||
|
||||
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
|
||||
Messaging.sendRequestForResult({
|
||||
type: "Reader:AddToList",
|
||||
@ -840,6 +843,7 @@ var BrowserApp = {
|
||||
NativeWindow.contextmenus._disableRestricted("BOOKMARK", NativeWindow.contextmenus.linkBookmarkableContext),
|
||||
function(aTarget) {
|
||||
UITelemetry.addEvent("action.1", "contextmenu", null, "web_bookmark");
|
||||
UITelemetry.addEvent("save.1", "contextmenu", null, "bookmark");
|
||||
|
||||
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
|
||||
let title = aTarget.textContent || aTarget.title || url;
|
||||
|
@ -115,6 +115,9 @@ user_pref("network.sntp.pools", "%(server)s");
|
||||
// at least once will mean that codepath is still tested in automation.
|
||||
user_pref("network.sntp.maxRetryCount", 1);
|
||||
|
||||
// Make sure the notification permission migration test doesn't hit the network.
|
||||
user_pref("browser.push.warning.infoURL", "http://%(server)s/alerts-dummy/infoURL");
|
||||
|
||||
// Existing tests don't wait for the notification button security delay
|
||||
user_pref("security.notification_enable_delay", 0);
|
||||
|
||||
|
@ -351,7 +351,7 @@ AboutReader.prototype = {
|
||||
url: this._article.url,
|
||||
title: this._article.title
|
||||
});
|
||||
UITelemetry.addEvent("share.1", "list", null);
|
||||
UITelemetry.addEvent("share.1", "list", null, "reader");
|
||||
},
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user