merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2016-04-05 16:50:25 +02:00
commit 4137d1150d
55 changed files with 794 additions and 639 deletions

View File

@ -1018,6 +1018,11 @@
</body>
</method>
<!-- Holds a unique ID for the tab change that's currently being timed.
Used to make sure that multiple, rapid tab switches do not try to
create overlapping timers. -->
<field name="_tabSwitchID">null</field>
<method name="updateCurrentBrowser">
<parameter name="aForceUpdate"/>
<body>
@ -1033,11 +1038,23 @@
// Waiting until the next MozAfterPaint ensures that we capture
// the time it takes to paint, upload the textures to the compositor,
// and then composite.
if (this._tabSwitchID) {
TelemetryStopwatch.cancel("FX_TAB_SWITCH_TOTAL_MS");
}
let tabSwitchID = Symbol();
TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_MS");
window.addEventListener("MozAfterPaint", function onMozAfterPaint() {
TelemetryStopwatch.finish("FX_TAB_SWITCH_TOTAL_MS");
this._tabSwitchID = tabSwitchID;
let onMozAfterPaint = () => {
if (this._tabSwitchID === tabSwitchID) {
TelemetryStopwatch.finish("FX_TAB_SWITCH_TOTAL_MS");
this._tabSwitchID = null;
}
window.removeEventListener("MozAfterPaint", onMozAfterPaint);
});
}
window.addEventListener("MozAfterPaint", onMozAfterPaint);
}
}

View File

@ -286,7 +286,7 @@ const CustomizableWidgets = [
}, {
id: "sync-button",
label: "remotetabs-panelmenu.label",
tooltiptext: "remotetabs-panelmenu.tooltiptext",
tooltiptext: "remotetabs-panelmenu.tooltiptext2",
type: "view",
viewId: "PanelUI-remotetabs",
defaultArea: CustomizableUI.AREA_PANEL,

View File

@ -1238,7 +1238,8 @@ BrowserGlue.prototype = {
if (AppConstants.E10S_TESTING_ONLY) {
E10SUINotification.checkStatus();
}
if (AppConstants.platform == "win") {
if (AppConstants.platform == "win" ||
AppConstants.platform == "macosx") {
// Handles prompting to inform about incompatibilites when accessibility
// and e10s are active together.
E10SAccessibilityCheck.init();

View File

@ -253,7 +253,7 @@ var gMainPane = {
let win = wm.getMostRecentWindow("navigator:browser");
if (win) {
let accountsTab = win.gBrowser.addTab("about:accounts");
let accountsTab = win.gBrowser.addTab("about:accounts?action=signin&entrypoint=dev-edition-setup");
win.gBrowser.selectedTab = accountsTab;
}
},

View File

@ -7,7 +7,7 @@ history-panelmenu.label = History
history-panelmenu.tooltiptext2 = Show your history (%S)
remotetabs-panelmenu.label = Synced Tabs
remotetabs-panelmenu.tooltiptext = Show your synced tabs from other devices
remotetabs-panelmenu.tooltiptext2 = Show tabs from other devices
privatebrowsing-button.label = New Private Window
# LOCALIZATION NOTE(privatebrowsing-button.tooltiptext): %S is the keyboard shortcut

View File

@ -315,7 +315,11 @@ var SourceUtils = {
}
// Prepend the hostname and port number.
if (aSeq == 4) {
let host = aUrl.hostPort;
let host;
try {
// Bug 1261860: jar: URLs throw when accessing `hostPost`
host = aUrl.hostPort;
} catch(e) {}
if (host) {
return this.trimUrl(aUrl, host + "/" + aLabel, aSeq + 1);
}

View File

@ -478,16 +478,18 @@ TabTarget.prototype = {
this.client.addListener("tabDetached", this._onTabDetached);
this._onTabNavigated = (aType, aPacket) => {
// Update the title and url on tabNavigated event.
this._url = aPacket.url;
this._title = aPacket.title;
let event = Object.create(null);
event.url = aPacket.url;
event.title = aPacket.title;
event.nativeConsoleAPI = aPacket.nativeConsoleAPI;
event.isFrameSwitching = aPacket.isFrameSwitching;
if (!aPacket.isFrameSwitching) {
// Update the title and url unless this is a frame switch.
this._url = aPacket.url;
this._title = aPacket.title;
}
// Send any stored event payload (DOMWindow or nsIRequest) for backwards
// compatibility with non-remotable tools.
if (aPacket.state == "start") {

View File

@ -7,6 +7,7 @@ support-files =
browser_toolbox_options_disable_cache.sjs
browser_toolbox_sidebar_tool.xul
browser_toolbox_window_title_changes_page.html
browser_toolbox_window_title_frame_select_page.html
code_math.js
code_ugly.js
head.js
@ -74,6 +75,7 @@ skip-if = e10s # Bug 1069044 - destroyInspector may hang during shutdown
[browser_toolbox_window_shortcuts.js]
skip-if = os == "mac" && os_version == "10.8" || os == "win" && os_version == "5.1" # Bug 851129 - Re-enable browser_toolbox_window_shortcuts.js test after leaks are fixed
[browser_toolbox_window_title_changes.js]
[browser_toolbox_window_title_frame_select.js]
[browser_toolbox_zoom.js]
[browser_two_tabs.js]
skip-if = e10s && debug && os == 'win' # Bug 1231869

View File

@ -0,0 +1,72 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from shared-head.js */
"use strict";
/**
* Check that the detached devtools window title is not updated when switching
* the selected frame.
*/
var {Toolbox} = require("devtools/client/framework/toolbox");
const URL = URL_ROOT + "browser_toolbox_window_title_frame_select_page.html";
const IFRAME_URL = URL_ROOT + "browser_toolbox_window_title_changes_page.html";
add_task(function* () {
Services.prefs.setBoolPref("devtools.command-button-frames.enabled", true);
yield addTab(URL);
let target = TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = yield gDevTools.showToolbox(target, null,
Toolbox.HostType.BOTTOM);
yield toolbox.selectTool("inspector");
yield toolbox.switchHost(Toolbox.HostType.WINDOW);
is(getTitle(), "Inspector - Page title",
"Devtools title correct after switching to detached window host");
// Verify that the frame list button is visible and populated
let btn = toolbox.doc.getElementById("command-button-frames");
let frames = Array.slice(btn.firstChild.querySelectorAll("[data-window-id]"));
is(frames.length, 2, "We have both frames in the list");
let topFrameBtn = frames.filter(b => b.getAttribute("label") == URL)[0];
let iframeBtn = frames.filter(b => b.getAttribute("label") == IFRAME_URL)[0];
ok(topFrameBtn, "Got top level document in the list");
ok(iframeBtn, "Got iframe document in the list");
// Listen to will-navigate to check if the view is empty
let willNavigate = toolbox.target.once("will-navigate");
// Only select the iframe after we are able to select an element from the top
// level document.
let newRoot = toolbox.getPanel("inspector").once("new-root");
info("Select the iframe");
iframeBtn.click();
yield willNavigate;
yield newRoot;
info("Navigation to the iframe is done, the inspector should be back up");
is(getTitle(), "Inspector - Page title",
"Devtools title was not updated after changing inspected frame");
info("Cleanup toolbox and test preferences.");
yield toolbox.destroy();
toolbox = null;
gBrowser.removeCurrentTab();
Services.prefs.clearUserPref("devtools.toolbox.host");
Services.prefs.clearUserPref("devtools.toolbox.selectedTool");
Services.prefs.clearUserPref("devtools.toolbox.sideEnabled");
Services.prefs.clearUserPref("devtools.command-button-frames.enabled");
finish();
});
function getTitle() {
return Services.wm.getMostRecentWindow("devtools:toolbox").document.title;
}

View File

@ -0,0 +1,11 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Page title</title>
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<iframe src="browser_toolbox_window_title_changes_page.html"></iframe>
</head>
<body></body>
</html>

View File

@ -79,7 +79,6 @@ function BrowserLoaderBuilder({ baseURI, window, useOnlyShared }) {
const loaderOptions = devtools.require("@loader/options");
const dynamicPaths = {};
const componentProxies = new Map();
const hotReloadEnabled = Services.prefs.getBoolPref("devtools.loader.hotreload");
if (AppConstants.DEBUG || AppConstants.DEBUG_JS_MODULES) {
dynamicPaths["devtools/client/shared/vendor/react"] =
@ -134,7 +133,7 @@ function BrowserLoaderBuilder({ baseURI, window, useOnlyShared }) {
}
};
if (hotReloadEnabled) {
if (Services.prefs.getBoolPref("devtools.loader.hotreload")) {
opts.loadModuleHook = (module, require) => {
const { uri, exports } = module;
@ -158,24 +157,19 @@ function BrowserLoaderBuilder({ baseURI, window, useOnlyShared }) {
}
return exports;
}
const watcher = devtools.require("devtools/client/shared/devtools-file-watcher");
let onFileChanged = (_, relativePath, path) => {
this.hotReloadFile(componentProxies, "resource://devtools/" + relativePath);
};
watcher.on("file-changed", onFileChanged);
window.addEventListener("unload", () => {
watcher.off("file-changed", onFileChanged);
});
}
const mainModule = loaders.Module(baseURI, joinURI(baseURI, "main.js"));
this.loader = loaders.Loader(opts);
this.require = loaders.Require(this.loader, mainModule);
if (hotReloadEnabled) {
const watcher = devtools.require("devtools/client/shared/file-watcher");
const onFileChanged = (_, relativePath) => {
this.hotReloadFile(window, componentProxies,
"resource://devtools/" + relativePath);
};
watcher.on("file-changed", onFileChanged);
window.addEventListener("unload", () => {
watcher.off("file-changed", onFileChanged);
});
}
}
BrowserLoaderBuilder.prototype = {
@ -201,7 +195,7 @@ BrowserLoaderBuilder.prototype = {
});
},
hotReloadFile: function(window, componentProxies, fileURI) {
hotReloadFile: function(componentProxies, fileURI) {
if (fileURI.match(/\.js$/)) {
// Test for React proxy components
const proxy = componentProxies.get(fileURI);

View File

@ -103,7 +103,7 @@ function replaceCSSResource(window, fileURI) {
function watchCSS(window) {
if (Services.prefs.getBoolPref("devtools.loader.hotreload")) {
const watcher = require("devtools/client/shared/file-watcher");
const watcher = require("devtools/client/shared/devtools-file-watcher");
function onFileChanged(_, relativePath) {
if (relativePath.match(/\.css$/)) {

View File

@ -0,0 +1,77 @@
/* 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 { Ci } = require("chrome");
const Services = require("Services");
const EventEmitter = require("devtools/shared/event-emitter");
loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
const HOTRELOAD_PREF = "devtools.loader.hotreload";
function resolveResourcePath(uri) {
const handler = Services.io.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
const resolved = handler.resolveURI(Services.io.newURI(uri, null, null));
return resolved.replace(/file:\/\//, "");
}
function findSourceDir(path) {
if (path === "" || path === "/") {
return Promise.resolve(null);
}
return OS.File.exists(
OS.Path.join(path, "devtools/client/shared/file-watcher.js")
).then(exists => {
if (exists) {
return path;
} else {
return findSourceDir(OS.Path.dirname(path));
}
});
}
let worker = null;
const onPrefChange = function() {
// We need to figure out a src dir to watch. These are the actual
// files the user is working with, not the files in the obj dir. We
// do this by walking up the filesystem and looking for the devtools
// directories, and falling back to the raw path. This means none of
// this will work for users who store their obj dirs outside of the
// src dir.
//
// We take care not to mess with the `devtoolsPath` if that's what
// we end up using, because it might be intentionally mapped to a
// specific place on the filesystem for loading devtools externally.
//
// `devtoolsPath` is currently the devtools directory inside of the
// obj dir, and we search for `devtools/client`, so go up 2 levels
// to skip that devtools dir and start searching for the src dir.
if (Services.prefs.getBoolPref(HOTRELOAD_PREF) && !worker) {
const devtoolsPath = resolveResourcePath("resource://devtools")
.replace(/\/$/, "");
const searchPoint = OS.Path.dirname(OS.Path.dirname(devtoolsPath));
findSourceDir(searchPoint)
.then(srcPath => {
const rootPath = srcPath ? OS.Path.join(srcPath, "devtools") : devtoolsPath;
const watchPath = OS.Path.join(rootPath, "client");
const { watchFiles } = require("devtools/client/shared/file-watcher");
worker = watchFiles(watchPath, path => {
let relativePath = path.replace(rootPath + "/", "");
module.exports.emit("file-changed", relativePath, path);
});
});
} else if (worker) {
worker.terminate();
worker = null;
}
}
Services.prefs.addObserver(HOTRELOAD_PREF, {
observe: onPrefChange
}, false);
onPrefChange();
EventEmitter.decorate(module.exports);

View File

@ -9,17 +9,6 @@ importScripts("resource://gre/modules/osfile.jsm");
const modifiedTimes = new Map();
function findSourceDir(path) {
if (path === "" || path === "/") {
return null;
} else if (OS.File.exists(
OS.Path.join(path, "devtools/client/shared/file-watcher.js")
)) {
return path;
}
return findSourceDir(OS.Path.dirname(path));
}
function gatherFiles(path, fileRegex) {
let files = [];
const iterator = new OS.File.DirectoryIterator(path);
@ -71,28 +60,8 @@ function scanFiles(files, onChangedFile) {
onmessage = function(event) {
const { path, fileRegex } = event.data;
const devtoolsPath = event.data.devtoolsPath.replace(/\/$/, "");
// We need to figure out a src dir to watch. These are the actual
// files the user is working with, not the files in the obj dir. We
// do this by walking up the filesystem and looking for the devtools
// directories, and falling back to the raw path. This means none of
// this will work for users who store their obj dirs outside of the
// src dir.
//
// We take care not to mess with the `devtoolsPath` if that's what
// we end up using, because it might be intentionally mapped to a
// specific place on the filesystem for loading devtools externally.
//
// `devtoolsPath` is currently the devtools directory inside of the
// obj dir, and we search for `devtools/client`, so go up 2 levels
// to skip that devtools dir and start searching for the src dir.
const searchPoint = OS.Path.dirname(OS.Path.dirname(devtoolsPath));
const srcPath = findSourceDir(searchPoint);
const rootPath = srcPath ? OS.Path.join(srcPath, "devtools") : devtoolsPath;
const watchPath = OS.Path.join(rootPath, path.replace(/^devtools\//, ""));
const info = OS.File.stat(watchPath);
const info = OS.File.stat(path);
if (!info.isDir) {
throw new Error("Watcher expects a directory as root path");
}
@ -100,14 +69,13 @@ onmessage = function(event) {
// We get a list of all the files upfront, which means we don't
// support adding new files. But you need to rebuild Firefox when
// adding a new file anyway.
const files = gatherFiles(watchPath, fileRegex || /.*/);
const files = gatherFiles(path, fileRegex || /.*/);
// Every second, scan for file changes by stat-ing each of them and
// comparing modification time.
setInterval(() => {
scanFiles(files, changedFile => {
postMessage({ fullPath: changedFile,
relativePath: changedFile.replace(rootPath + "/", "") });
postMessage({ path: changedFile });
});
}, 1000);
};

View File

@ -5,22 +5,8 @@
const { Ci, ChromeWorker } = require("chrome");
const Services = require("Services");
const EventEmitter = require("devtools/shared/event-emitter");
const HOTRELOAD_PREF = "devtools.loader.hotreload";
function resolveResourcePath(uri) {
const handler = Services.io.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
const resolved = handler.resolveURI(Services.io.newURI(uri, null, null));
return resolved.replace(/file:\/\//, "");
}
function watchFiles(path, onFileChanged) {
if (!path.startsWith("devtools/")) {
throw new Error("`watchFiles` expects a devtools path");
}
const watchWorker = new ChromeWorker(
"resource://devtools/client/shared/file-watcher-worker.js"
);
@ -30,35 +16,14 @@ function watchFiles(path, onFileChanged) {
// chrome). This means that this system will only work when built
// files are symlinked, so that these URIs actually read from
// local sources. There might be a better way to do this.
const { relativePath, fullPath } = event.data;
onFileChanged(relativePath, fullPath);
const { path } = event.data;
onFileChanged(path);
};
watchWorker.postMessage({
path: path,
// We must do this here because we can't access the needed APIs in
// a worker.
devtoolsPath: resolveResourcePath("resource://devtools"),
fileRegex: /\.(js|css|svg|png)$/ });
path,
fileRegex: /\.(js|css|svg|png)$/
});
return watchWorker;
}
EventEmitter.decorate(module.exports);
let watchWorker;
function onPrefChange() {
if (Services.prefs.getBoolPref(HOTRELOAD_PREF) && !watchWorker) {
watchWorker = watchFiles("devtools/client", (relativePath, fullPath) => {
module.exports.emit("file-changed", relativePath, fullPath);
});
} else if (watchWorker) {
watchWorker.terminate();
watchWorker = null;
}
}
Services.prefs.addObserver(HOTRELOAD_PREF, {
observe: onPrefChange
}, false);
onPrefChange();
exports.watchFiles = watchFiles;

View File

@ -382,7 +382,7 @@ InplaceEditor.prototype = {
}
}
copyTextStyles(this.input, this._measurement);
copyAllStyles(this.input, this._measurement);
this._updateSize();
},
@ -1367,6 +1367,17 @@ function copyTextStyles(from, to) {
to.style.fontSize = getCssText("font-size");
to.style.fontWeight = getCssText("font-weight");
to.style.fontStyle = getCssText("font-style");
}
/**
* Copy all styles which could have an impact on the element size.
*/
function copyAllStyles(from, to) {
let win = from.ownerDocument.defaultView;
let style = win.getComputedStyle(from);
let getCssText = name => style.getPropertyCSSValue(name).cssText;
copyTextStyles(from, to);
to.style.lineHeight = getCssText("line-height");
// If box-sizing is set to border-box, box model styles also need to be

View File

@ -24,6 +24,7 @@ DevToolsModules(
'demangle.js',
'developer-toolbar.js',
'devices.js',
'devtools-file-watcher.js',
'DOMHelpers.jsm',
'doorhanger.js',
'file-watcher-worker.js',

View File

@ -6,7 +6,6 @@
var { editableField } = require("devtools/client/shared/inplace-editor");
const LINE_HEIGHT = 15;
const MAX_WIDTH = 300;
const START_TEXT = "Start text";
const LONG_TEXT = "I am a long text and I will not fit in a 300px container. " +
@ -95,7 +94,10 @@ let testMaxWidth = Task.async(function* (editor) {
* @return {Number} the number of lines
*/
function getLines(textarea) {
return Math.floor(textarea.clientHeight / LINE_HEIGHT);
let win = textarea.ownerDocument.defaultView;
let style = win.getComputedStyle(textarea);
let lineHeight = style.getPropertyCSSValue("line-height").cssText;
return Math.floor(textarea.clientHeight / parseFloat(lineHeight));
}
/**
@ -125,7 +127,6 @@ function createSpan(doc) {
info("Creating a new span element");
let span = doc.createElement("span");
span.setAttribute("tabindex", "0");
span.style.lineHeight = LINE_HEIGHT + "px";
span.style.fontSize = "11px";
span.style.fontFamily = "monospace";
span.textContent = START_TEXT;

View File

@ -1,3 +1,45 @@
### Getting data from the store
To get data from the store, use `connect()`.
When using connect, you'll break up your component into two parts:
1. The part that displays the data (presentational component)
// todos.js
const Todos = React.createClass({
propTypes: {
todos: PropTypes.array.isRequired
}
render: function() {...}
})
module.exports = Todos;
2. The part that gets the data from the store (container component)
// todos-container.js
const Todos = require("path/to/todos");
function mapStateToProps(state) {
return {
todos: state.todos
};
}
module.exports = connect(mapStateToProps)(Todos);
`connect()` generates the container component. It wraps around the presentational component that was passed in (e.g. Todos).
The `mapStateToProps` is often called a selector. That's because it selects data from the state object. When the container component is rendering, the the selector will be called. It will pick out the data that the presentational component is going to need. Redux will take this object and pass it in to the presentational component as props.
With this setup, a presentational component is easy to share across different apps. It doesn't have any dependencies on the app, or any hardcoded expectations about how to get data. It just gets props that are passed to it and renders them.
For more advanced use cases, you can pass additional parameters into the selector and `connect()` functions. Read about those in the [`connect()`](https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options) docs.
---
Need to answer the following questions:
@ -7,4 +49,4 @@ Need to answer the following questions:
* What file structure should I use?
* How do I test redux code?
And more.
And more.

View File

@ -10,9 +10,12 @@
#include ../search/manifests/SearchAndroidManifest_permissions.xml.in
#endif
#ifdef MOZ_ANDROID_GCM
<!-- Bug 1261302: we have two new permissions to request,
RECEIVE_BOOT_COMPLETED and the permission for push. We want to ask for
them during the same release, which should be Fennec 48. Therefore we
decouple the push permission from MOZ_ANDROID_GCM to let it ride ahead
(potentially) of the push feature. -->
#include GcmAndroidManifest_permissions.xml.in
#endif
<!-- A signature level permission specific to each Firefox version (Android
package name, e.g., org.mozilla.firefox). Use this permission to

View File

@ -375,7 +375,7 @@ res/raw/browsersearch.json: .locales.deps ;
res/raw/suggestedsites.json: .locales.deps ;
all_resources = \
$(abspath $(CURDIR)/AndroidManifest.xml) \
$(DEPTH)/mobile/android/base/AndroidManifest.xml \
$(android_res_files) \
$(ANDROID_GENERATED_RESFILES) \
$(NULL)
@ -496,8 +496,8 @@ ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
.aapt.nodeps: FORCE
cp $(gradle_dir)/app/intermediates/res/resources-automation-debug.ap_ gecko-nodeps.ap_
else
# .aapt.nodeps: $(CURDIR)/AndroidManifest.xml FORCE
$(eval $(call aapt_command,.aapt.nodeps,$(CURDIR)/AndroidManifest.xml FORCE,gecko-nodeps.ap_,gecko-nodeps/,gecko-nodeps/))
# .aapt.nodeps: $(DEPTH)/mobile/android/base/AndroidManifest.xml FORCE
$(eval $(call aapt_command,.aapt.nodeps,$(DEPTH)/mobile/android/base/AndroidManifest.xml FORCE,gecko-nodeps.ap_,gecko-nodeps/,gecko-nodeps/))
endif
# Override the Java settings with some specific android settings
@ -525,7 +525,7 @@ $(ABS_DIST)/fennec/$(OMNIJAR_NAME): FORCE
# Targets built very early during a Gradle build.
gradle-targets: $(foreach f,$(constants_PP_JAVAFILES),$(f))
gradle-targets: $(abspath AndroidManifest.xml)
gradle-targets: $(DEPTH)/mobile/android/base/AndroidManifest.xml
gradle-targets: $(ANDROID_GENERATED_RESFILES)
ifndef MOZILLA_OFFICIAL

View File

@ -1770,7 +1770,6 @@ public class BrowserApp extends GeckoApp
Telemetry.addToHistogram("FENNEC_BOOKMARKS_COUNT", db.getCount(cr, "bookmarks"));
Telemetry.addToHistogram("FENNEC_READING_LIST_COUNT", db.getReadingListAccessor().getCount(cr));
Telemetry.addToHistogram("BROWSER_IS_USER_DEFAULT", (isDefaultBrowser(Intent.ACTION_VIEW) ? 1 : 0));
Telemetry.addToHistogram("FENNEC_TABQUEUE_ENABLED", (TabQueueHelper.isTabQueueEnabled(BrowserApp.this) ? 1 : 0));
Telemetry.addToHistogram("FENNEC_CUSTOM_HOMEPAGE", (TextUtils.isEmpty(getHomepage()) ? 0 : 1));
final SharedPreferences prefs = GeckoSharedPrefs.forProfile(getContext());
final boolean hasCustomHomepanels = prefs.contains(HomeConfigPrefsBackend.PREFS_CONFIG_KEY) || prefs.contains(HomeConfigPrefsBackend.PREFS_CONFIG_KEY_OLD);
@ -4093,17 +4092,23 @@ public class BrowserApp extends GeckoApp
}
@Override
protected StartupAction getStartupAction(final String passedURL, final String action) {
final boolean inGuestMode = GeckoProfile.get(this).inGuestMode();
if (inGuestMode) {
return StartupAction.GUEST;
}
if (Restrictions.isRestrictedProfile(this)) {
return StartupAction.RESTRICTED;
}
protected void recordStartupActionTelemetry(final String passedURL, final String action) {
final TelemetryContract.Method method;
if (ACTION_HOMESCREEN_SHORTCUT.equals(action)) {
return StartupAction.SHORTCUT;
// This action is also recorded via "loadurl.1" > "homescreen".
method = TelemetryContract.Method.HOMESCREEN;
} else if (passedURL == null) {
Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, TelemetryContract.Method.HOMESCREEN, "launcher");
method = TelemetryContract.Method.HOMESCREEN;
} else {
// This is action is also recorded via "loadurl.1" > "intent".
method = TelemetryContract.Method.INTENT;
}
if (GeckoProfile.get(this).inGuestMode()) {
Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, method, "guest");
} else if (Restrictions.isRestrictedProfile(this)) {
Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, method, "restricted");
}
return (passedURL == null ? StartupAction.NORMAL : StartupAction.URL);
}
}

View File

@ -141,15 +141,6 @@ public abstract class GeckoApp
private static final String LOGTAG = "GeckoApp";
private static final int ONE_DAY_MS = 1000*60*60*24;
public enum StartupAction {
NORMAL, /* normal application start */
URL, /* launched with a passed URL */
PREFETCH, /* launched with a passed URL that we prefetch */
GUEST, /* launched in guest browsing */
RESTRICTED, /* launched with restricted profile */
SHORTCUT /* launched from a homescreen shortcut */
}
public static final String ACTION_ALERT_CALLBACK = "org.mozilla.gecko.ACTION_ALERT_CALLBACK";
public static final String ACTION_HOMESCREEN_SHORTCUT = "org.mozilla.gecko.BOOKMARK";
public static final String ACTION_DEBUG = "org.mozilla.gecko.DEBUG";
@ -1537,8 +1528,7 @@ public abstract class GeckoApp
getProfile().moveSessionFile();
}
final StartupAction startupAction = getStartupAction(passedUri, action);
Telemetry.addToHistogram("FENNEC_GECKOAPP_STARTUP_ACTION", startupAction.ordinal());
recordStartupActionTelemetry(passedUri, action);
// Check if launched from data reporting notification.
if (ACTION_LAUNCH_SETTINGS.equals(action)) {
@ -1945,8 +1935,7 @@ public abstract class GeckoApp
startActivity(settingsIntent);
}
final StartupAction startupAction = getStartupAction(passedUri, action);
Telemetry.addToHistogram("FENNEC_GECKOAPP_STARTUP_ACTION", startupAction.ordinal());
recordStartupActionTelemetry(passedUri, action);
}
/**
@ -2762,9 +2751,7 @@ public abstract class GeckoApp
return new StubbedHealthRecorder();
}
protected StartupAction getStartupAction(final String passedURL, final String action) {
// Default to NORMAL here. Subclasses can handle the other types.
return StartupAction.NORMAL;
protected void recordStartupActionTelemetry(final String passedURL, final String action) {
}
@Override

View File

@ -9,6 +9,7 @@ import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import android.annotation.TargetApi;
import android.content.Intent;
@ -49,20 +50,18 @@ public class TabQueuePrompt extends Locales.LocaleAwareActivity {
private void showTabQueueEnablePrompt() {
setContentView(R.layout.tab_queue_prompt);
final int numberOfTimesTabQueuePromptSeen = GeckoSharedPrefs.forApp(this).getInt(TabQueueHelper.PREF_TAB_QUEUE_TIMES_PROMPT_SHOWN, 0);
final View okButton = findViewById(R.id.ok_button);
okButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onConfirmButtonPressed();
Telemetry.addToHistogram("FENNEC_TABQUEUE_PROMPT_ENABLE_YES", numberOfTimesTabQueuePromptSeen);
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "tabqueue_prompt_yes");
}
});
findViewById(R.id.cancel_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Telemetry.addToHistogram("FENNEC_TABQUEUE_PROMPT_ENABLE_NO", numberOfTimesTabQueuePromptSeen);
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "tabqueue_prompt_no");
setResult(TabQueueHelper.TAB_QUEUE_NO);
finish();
}
@ -168,8 +167,7 @@ public class TabQueuePrompt extends Locales.LocaleAwareActivity {
if (TabQueueHelper.canDrawOverlays(this)) {
// User granted the permission in Android's settings.
final int numberOfTimesTabQueuePromptSeen = GeckoSharedPrefs.forApp(this).getInt(TabQueueHelper.PREF_TAB_QUEUE_TIMES_PROMPT_SHOWN, 0);
Telemetry.addToHistogram("FENNEC_TABQUEUE_PROMPT_ENABLE_YES", numberOfTimesTabQueuePromptSeen);
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "tabqueue_prompt_yes");
setResult(TabQueueHelper.TAB_QUEUE_YES);
finish();

View File

@ -43,16 +43,11 @@ var MemoryObserver = {
let data = browser.__SS_data;
let extra = browser.__SS_extdata;
// Destroying the tab will stop audio playback without invoking the
// normal events, therefore we need to explicitly tell the Java UI
// to stop displaying the audio playing indicator.
if (tab.playingAudio) {
Messaging.sendRequest({
type: "Tab:AudioPlayingChange",
tabID: tab.id,
isAudioPlaying: false
});
}
// Notify any interested parties (e.g. the session store)
// that the original tab object is going to be destroyed
let evt = document.createEvent("UIEvents");
evt.initUIEvent("TabPreZombify", true, false, window, null);
browser.dispatchEvent(evt);
// We need this data to correctly create and position the new browser
// If this browser is already a zombie, fallback to the session data
@ -69,6 +64,11 @@ var MemoryObserver = {
browser.__SS_extdata = extra;
browser.__SS_restore = true;
browser.setAttribute("pending", "true");
// Notify the session store to reattach its listeners to the new tab object
evt = document.createEvent("UIEvents");
evt.initUIEvent("TabPostZombify", true, false, window, null);
browser.dispatchEvent(evt);
},
gc: function() {

View File

@ -3465,6 +3465,7 @@ Tab.prototype = {
this.browser.addEventListener("MozScrolledAreaChanged", this, true);
this.browser.addEventListener("pageshow", this, true);
this.browser.addEventListener("MozApplicationManifest", this, true);
this.browser.addEventListener("TabPreZombify", this, true);
// Note that the XBL binding is untrusted
this.browser.addEventListener("PluginBindingAttached", this, true, true);
@ -3572,6 +3573,7 @@ Tab.prototype = {
this.browser.removeEventListener("MozScrolledAreaChanged", this, true);
this.browser.removeEventListener("pageshow", this, true);
this.browser.removeEventListener("MozApplicationManifest", this, true);
this.browser.removeEventListener("TabPreZombify", this, true);
this.browser.removeEventListener("PluginBindingAttached", this, true, true);
this.browser.removeEventListener("VideoBindingAttached", this, true, true);
@ -4095,7 +4097,8 @@ Tab.prototype = {
}
case "DOMAudioPlaybackStarted":
case "DOMAudioPlaybackStopped": {
case "DOMAudioPlaybackStopped":
case "TabPreZombify": {
if (!Services.prefs.getBoolPref("browser.tabs.showAudioPlayingIcon") ||
!aEvent.isTrusted) {
return;

View File

@ -213,6 +213,16 @@ SessionStore.prototype = {
this.onTabRemove(window, browser);
break;
}
case "TabPreZombify": {
let browser = aEvent.target;
this.onTabRemove(window, browser, true);
break;
}
case "TabPostZombify": {
let browser = aEvent.target;
this.onTabAdd(window, browser, true);
break;
}
case "TabSelect": {
let browser = aEvent.target;
this.onTabSelect(window, browser);
@ -277,11 +287,13 @@ SessionStore.prototype = {
for (let i = 0; i < tabs.length; i++)
this.onTabAdd(aWindow, tabs[i].browser, true);
// Notification of tab add/remove/selection
// Notification of tab add/remove/selection/zombification
let browsers = aWindow.document.getElementById("browsers");
browsers.addEventListener("TabOpen", this, true);
browsers.addEventListener("TabClose", this, true);
browsers.addEventListener("TabSelect", this, true);
browsers.addEventListener("TabPreZombify", this, true);
browsers.addEventListener("TabPostZombify", this, true);
},
onWindowClose: function ss_onWindowClose(aWindow) {
@ -293,6 +305,8 @@ SessionStore.prototype = {
browsers.removeEventListener("TabOpen", this, true);
browsers.removeEventListener("TabClose", this, true);
browsers.removeEventListener("TabSelect", this, true);
browsers.removeEventListener("TabPreZombify", this, true);
browsers.removeEventListener("TabPostZombify", this, true);
if (this._loadState == STATE_RUNNING) {
// Update all window data for a last time
@ -972,6 +986,8 @@ SessionStore.prototype = {
};
let tab = window.BrowserApp.addTab(tabData.entries[tabData.index - 1].url, params);
tab.browser.__SS_data = tabData;
tab.browser.__SS_extdata = tabData.extData;
this._restoreTab(tabData, tab.browser);
}
},
@ -991,12 +1007,18 @@ SessionStore.prototype = {
* point, but text data must be delayed until the content loads.
*/
_restoreTab: function ss_restoreTab(aTabData, aBrowser) {
// aTabData shouldn't be empty here, but if it is,
// _restoreHistory() will crash otherwise.
if (!aTabData || aTabData.entries.length == 0) {
Cu.reportError("SessionStore.js: Error trying to restore tab with empty tabdata");
return;
}
this._restoreHistory(aTabData, aBrowser.sessionHistory);
// Restoring the text data requires waiting for the content to load. So
// we set a flag and delay this until the "load" event.
//this._restoreTextData(aTabData, aBrowser);
aBrowser.__SS_restore_data = aTabData || {};
aBrowser.__SS_restore_data = aTabData;
},
/**
@ -1094,19 +1116,19 @@ SessionStore.prototype = {
}
}
tab.browser.__SS_data = tabData;
tab.browser.__SS_extdata = tabData.extData;
if (window.BrowserApp.selectedTab == tab) {
this._restoreTab(tabData, tab.browser);
delete tab.browser.__SS_restore;
tab.browser.removeAttribute("pending");
} else {
// Make sure the browser has its session data for the delay reload
tab.browser.__SS_data = tabData;
// Mark the browser for delay loading
tab.browser.__SS_restore = true;
tab.browser.setAttribute("pending", "true");
}
tab.browser.__SS_extdata = tabData.extData;
}
// Restore the closed tabs array on the current window.
@ -1153,13 +1175,12 @@ SessionStore.prototype = {
tabIndex: this._lastClosedTabIndex
};
let tab = aWindow.BrowserApp.addTab(aCloseTabData.entries[aCloseTabData.index - 1].url, params);
tab.browser.__SS_data = aCloseTabData;
tab.browser.__SS_extdata = aCloseTabData.extData;
this._restoreTab(aCloseTabData, tab.browser);
this._lastClosedTabIndex = -1;
// Put back the extra data
tab.browser.__SS_extdata = aCloseTabData.extData;
if (this._notifyClosedTabs) {
this._sendClosedTabsToJava(aWindow);
}

View File

@ -38,6 +38,7 @@ skip-if = true # Bug 1241478
[test_restricted_profiles.html]
[test_selectoraddtab.html]
[test_session_form_data.html]
[test_session_zombification.html]
[test_shared_preferences.html]
[test_simple_discovery.html]
[test_video_discovery.html]

View File

@ -19,6 +19,19 @@ function promiseBrowserEvent(browser, eventType) {
});
}
function promiseTabEvent(container, eventType) {
return new Promise((resolve) => {
function handle(event) {
info("Received event " + eventType + " from container");
container.removeEventListener(eventType, handle, true);
resolve(event);
}
container.addEventListener(eventType, handle, true);
info("Now waiting for " + eventType + " event from container");
});
}
function promiseNotification(topic) {
Cu.import("resource://gre/modules/Services.jsm");

View File

@ -21,19 +21,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1216047
Cu.import("resource://gre/modules/Messaging.jsm");
Cu.import("resource://gre/modules/Task.jsm");
function promiseTabEvent(container, eventType) {
return new Promise((resolve) => {
function handle(event) {
info("Received event " + eventType + " from container");
container.removeEventListener(eventType, handle, true);
resolve(event);
}
container.addEventListener(eventType, handle, true);
info("Now waiting for " + eventType + " event from container");
});
}
// The chrome window
let chromeWin;

View File

@ -0,0 +1,110 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1044556
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1044556</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
<script type="application/javascript" src="head.js"></script>
<script type="application/javascript">
/** Test for Bug 1044556 **/
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Messaging.jsm");
Cu.import("resource://gre/modules/Task.jsm");
// Import the MemoryObserver
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "MemoryObserver", function() {
let sandbox = {};
Services.scriptloader.loadSubScript("chrome://browser/content/MemoryObserver.js", sandbox);
return sandbox["MemoryObserver"];
});
// The chrome window
let chromeWin;
// Track the tabs where the tests are happening
let tabBlank;
let tabTest;
const url1 = "data:text/html;charset=utf-8,It%20was%20a%20dark%20and%20stormy%20night.";
const url2 = "data:text/html;charset=utf-8,Suddenly%2C%20a%20tab%20was%20zombified.";
add_task(function* test_sessionStoreZombify() {
chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
let BrowserApp = chromeWin.BrowserApp;
SimpleTest.registerCleanupFunction(function() {
BrowserApp.closeTab(tabBlank);
BrowserApp.closeTab(tabTest);
});
// Add a new tab with some content
tabTest = BrowserApp.addTab(url1 , { selected: true, parentId: BrowserApp.selectedTab.id });
yield promiseBrowserEvent(tabTest.browser, "DOMContentLoaded");
// Add a new tab with a blank page
tabBlank = BrowserApp.addTab("about:blank", { selected: true, parentId: BrowserApp.selectedTab.id });
is(BrowserApp.selectedTab, tabBlank, "Test tab is in background.");
// Zombify the backgrounded test tab
MemoryObserver.zombify(tabTest);
// Check that the test tab is actually zombified
ok(tabTest.browser.__SS_restore, "Test tab is set for delay loading.");
// Switch back to the test tab and wait for it to reload
BrowserApp.selectTab(tabTest);
yield promiseBrowserEvent(tabTest.browser, "DOMContentLoaded");
// Check that the test tab has loaded the correct url
is(tabTest.browser.currentURI.spec, url1, "Test tab is showing the first URL.");
// Navigate to some other content
BrowserApp.loadURI(url2, tabTest.browser);
yield promiseBrowserEvent(tabTest.browser, "DOMContentLoaded");
is(tabTest.browser.currentURI.spec, url2, "Test tab is showing the second URL.");
// Switch to the other tab
BrowserApp.selectTab(tabBlank);
yield promiseTabEvent(BrowserApp.deck, "TabSelect");
is(BrowserApp.selectedTab, tabBlank, "Test tab is in background.");
// Zombify the backgrounded test tab again
MemoryObserver.zombify(tabTest);
// Check that the test tab is actually zombified
ok(tabTest.browser.__SS_restore, "Test tab is set for delay loading.");
// Switch back to the test tab and wait for it to reload
BrowserApp.selectTab(tabTest);
yield promiseBrowserEvent(tabTest.browser, "DOMContentLoaded");
// Check that the test tab has loaded the correct url
is(tabTest.browser.currentURI.spec, url2, "Test tab is showing the second URL.");
});
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1044556">Mozilla Bug 1044556</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
</html>

View File

@ -671,34 +671,16 @@ ExtensionData.prototype = {
});
},
// Reads the extension's |manifest.json| file and optional |mozilla.json|,
// and stores the parsed and merged contents in |this.manifest|.
// Reads the extension's |manifest.json| file, and stores its
// parsed contents in |this.manifest|.
readManifest() {
return Promise.all([
this.readJSON("manifest.json"),
this.readJSON("mozilla.json").catch(err => null),
Management.lazyInit(),
]).then(([manifest, mozManifest]) => {
]).then(([manifest]) => {
this.manifest = manifest;
this.rawManifest = manifest;
if (mozManifest) {
if (typeof mozManifest != "object") {
this.logger.warn(`Loading extension '${this.id}': mozilla.json has unexpected type ${typeof mozManifest}`);
} else {
Object.keys(mozManifest).forEach(key => {
if (key != "applications") {
throw new Error(`Illegal property "${key}" in mozilla.json`);
}
if (key in manifest) {
this.logger.warn(`Ignoring property "${key}" from mozilla.json`);
} else {
manifest[key] = mozManifest[key];
}
});
}
}
if (manifest && manifest.default_locale) {
return this.initLocale();
}

View File

@ -120,7 +120,7 @@ var api = context => {
i18n: {
getMessage: function(messageName, substitutions) {
return context.extension.localizeMessage(messageName, substitutions);
return context.extension.localizeMessage(messageName, substitutions, {cloneScope: context.cloneScope});
},
getAcceptLanguages: function(callback) {

View File

@ -317,6 +317,7 @@ function LocaleData(data) {
this.defaultLocale = data.defaultLocale;
this.selectedLocale = data.selectedLocale;
this.locales = data.locales || new Map();
this.warnedMissingKeys = new Set();
// Map(locale-name -> Map(message-key -> localized-string))
//
@ -348,8 +349,16 @@ LocaleData.prototype = {
},
// https://developer.chrome.com/extensions/i18n
localizeMessage(message, substitutions = [], locale = this.selectedLocale, defaultValue = "??") {
let locales = new Set([this.BUILTIN, locale, this.defaultLocale]
localizeMessage(message, substitutions = [], options = {}) {
let defaultOptions = {
locale: this.selectedLocale,
defaultValue: "",
cloneScope: null,
};
options = Object.assign(defaultOptions, options);
let locales = new Set([this.BUILTIN, options.locale, this.defaultLocale]
.filter(locale => this.messages.has(locale)));
// Message names are case-insensitive, so normalize them to lower-case.
@ -398,8 +407,15 @@ LocaleData.prototype = {
}
}
Cu.reportError(`Unknown localization message ${message}`);
return defaultValue;
if (!this.warnedMissingKeys.has(message)) {
let error = `Unknown localization message ${message}`;
if (options.cloneScope) {
error = new options.cloneScope.Error(error);
}
Cu.reportError(error);
this.warnedMissingKeys.add(message);
}
return options.defaultValue;
},
// Localize a string, replacing all |__MSG_(.*)__| tokens with the
@ -414,7 +430,7 @@ LocaleData.prototype = {
}
return str.replace(/__MSG_([A-Za-z0-9@_]+?)__/g, (matched, message) => {
return this.localizeMessage(message, [], locale, matched);
return this.localizeMessage(message, [], {locale, defaultValue: matched});
});
},

View File

@ -11,7 +11,7 @@ extensions.registerSchemaAPI("i18n", null, (extension, context) => {
return {
i18n: {
getMessage: function(messageName, substitutions) {
return extension.localizeMessage(messageName, substitutions);
return extension.localizeMessage(messageName, substitutions, {cloneScope: context.cloneScope});
},
getAcceptLanguages: function() {

View File

@ -34,6 +34,7 @@ function WebRequestEventManager(context, eventName) {
let data2 = {
requestId: data.requestId,
url: data.url,
originUrl: data.originUrl,
method: data.method,
type: data.type,
timeStamp: Date.now(),

View File

@ -28,5 +28,16 @@
<iframe src="data:text/plain,webRequestTest" width="200" height="200"></iframe>
<iframe src="data:text/plain,webRequestTest_bad" width="200" height="200"></iframe>
<iframe src="https://invalid.localhost/" width="200" height="200"></iframe>
<a href="file_WebRequest_page3.html?trigger=a" target="webrequest_link">link</a>
<form method="post" action="file_WebRequest_page3.html?trigger=form" target="webrequest_form"></form>
<script>
"use strict";
for (let a of document.links) {
a.click();
}
for (let f of document.forms) {
f.submit();
}
</script>
</body>
</html>

View File

@ -0,0 +1,11 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<script>
"use strict";
window.close();
</script>
</head>
</html>

View File

@ -4,6 +4,7 @@ support-files =
head.js
file_WebRequest_page1.html
file_WebRequest_page2.html
file_WebRequest_page3.html
file_WebNavigation_page1.html
file_WebNavigation_page2.html
file_WebNavigation_page3.html

View File

@ -28,10 +28,10 @@ add_task(function* test_i18n() {
assertEq("(bar)", _("bar"), "Simple message fallback in default locale.");
assertEq("??", _("some-unknown-locale-string"), "Unknown locale string.");
assertEq("", _("some-unknown-locale-string"), "Unknown locale string.");
assertEq("??", _("@@unknown_builtin_string"), "Unknown built-in string.");
assertEq("??", _("@@bidi_unknown_builtin_string"), "Unknown built-in bidi string.");
assertEq("", _("@@unknown_builtin_string"), "Unknown built-in string.");
assertEq("", _("@@bidi_unknown_builtin_string"), "Unknown built-in bidi string.");
assertEq("Føo.", _("Föo"), "Multi-byte message in selected locale.");

View File

@ -98,7 +98,8 @@ function backgroundScript() {
let savedTabId = -1;
function shouldRecord(url) {
return url.startsWith(BASE) || /^data:.*\bwebRequestTest|\/invalid\./.test(url);
return url.startsWith(BASE) && !url.includes("_page3.html") ||
/^data:.*\bwebRequestTest|\/invalid\./.test(url);
}
let statuses = [
@ -115,6 +116,12 @@ function backgroundScript() {
}
}
function checkOrigin(details) {
let isCorrectOrigin = details.url.includes("_page1.html") ? details.originUrl.endsWith("/test_ext_webrequest.html")
: /\/file_WebRequest_page\d\.html\b/.test(details.originUrl);
browser.test.assertTrue(isCorrectOrigin, `originUrl for ${details.url} is correct (${details.originUrl})`);
}
function checkType(details) {
let expected_type = "???";
if (details.url.includes("style")) {
@ -280,6 +287,7 @@ function backgroundScript() {
requestIDs.set(details.url, new Set([details.requestId]));
}
checkResourceType(details.type);
checkOrigin(details);
if (shouldRecord(details.url)) {
recorded.requested.push(details.url);
@ -311,6 +319,7 @@ function backgroundScript() {
function onBeforeSendHeaders(details) {
browser.test.log(`onBeforeSendHeaders ${details.url}`);
checkRequestId(details);
checkOrigin(details);
checkResourceType(details.type);
processHeaders("request", details);
if (shouldRecord(details.url)) {
@ -331,6 +340,7 @@ function backgroundScript() {
function onBeforeRedirect(details) {
browser.test.log(`onBeforeRedirect ${details.url} -> ${details.redirectUrl}`);
checkRequestId(details, "redirect");
checkOrigin(details);
checkResourceType(details.type);
if (shouldRecord(details.url)) {
recorded.beforeRedirect.push(details.url);
@ -354,6 +364,7 @@ function backgroundScript() {
browser.test.log(`${kind} ${details.requestId} ${details.url}`);
checkResourceType(details.type);
checkRequestId(details, kind);
checkOrigin(details);
if (kind in recorded && shouldRecord(details.url)) {
recorded[kind].push(details.url);
}

View File

@ -30,7 +30,7 @@
static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000;
wchar_t* MakeCommandLine(int argc, wchar_t** argv);
BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode);
BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath,
LPCWSTR newFileName);
/*
@ -479,79 +479,6 @@ ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv)
return result;
}
/**
* Obtains the updater path alongside a subdir of the service binary.
* The purpose of this function is to return a path that is likely high
* integrity and therefore more safe to execute code from.
*
* @param serviceUpdaterPath Out parameter for the path where the updater
* should be copied to.
* @return TRUE if a file path was obtained.
*/
BOOL
GetSecureUpdaterPath(WCHAR serviceUpdaterPath[MAX_PATH + 1])
{
if (!GetModuleFileNameW(nullptr, serviceUpdaterPath, MAX_PATH)) {
LOG_WARN(("Could not obtain module filename when attempting to "
"use a secure updater path. (%d)", GetLastError()));
return FALSE;
}
if (!PathRemoveFileSpecW(serviceUpdaterPath)) {
LOG_WARN(("Couldn't remove file spec when attempting to use a secure "
"updater path. (%d)", GetLastError()));
return FALSE;
}
if (!PathAppendSafe(serviceUpdaterPath, L"update")) {
LOG_WARN(("Couldn't append file spec when attempting to use a secure "
"updater path. (%d)", GetLastError()));
return FALSE;
}
CreateDirectoryW(serviceUpdaterPath, nullptr);
if (!PathAppendSafe(serviceUpdaterPath, L"updater.exe")) {
LOG_WARN(("Couldn't append file spec when attempting to use a secure "
"updater path. (%d)", GetLastError()));
return FALSE;
}
return TRUE;
}
/**
* Deletes the passed in updater path and the associated updater.ini file.
*
* @param serviceUpdaterPath The path to delete.
* @return TRUE if a file was deleted.
*/
BOOL
DeleteSecureUpdater(WCHAR serviceUpdaterPath[MAX_PATH + 1])
{
BOOL result = FALSE;
if (serviceUpdaterPath[0]) {
result = DeleteFileW(serviceUpdaterPath);
if (!result && GetLastError() != ERROR_PATH_NOT_FOUND &&
GetLastError() != ERROR_FILE_NOT_FOUND) {
LOG_WARN(("Could not delete service updater path: '%ls'.",
serviceUpdaterPath));
}
WCHAR updaterINIPath[MAX_PATH + 1] = { L'\0' };
if (PathGetSiblingFilePath(updaterINIPath, serviceUpdaterPath,
L"updater.ini")) {
result = DeleteFileW(updaterINIPath);
if (!result && GetLastError() != ERROR_PATH_NOT_FOUND &&
GetLastError() != ERROR_FILE_NOT_FOUND) {
LOG_WARN(("Could not delete service updater INI path: '%ls'.",
updaterINIPath));
}
}
}
return result;
}
/**
* Executes a service command.
*
@ -584,52 +511,7 @@ ExecuteServiceCommand(int argc, LPWSTR *argv)
BOOL result = FALSE;
if (!lstrcmpi(argv[2], L"software-update")) {
// Use the passed in command line arguments for the update, except for the
// path to updater.exe. We copy updater.exe to a the directory of the
// MozillaMaintenance service so that a low integrity process cannot
// replace the updater.exe at any point and use that for the update.
// It also makes DLL injection attacks harder.
LPWSTR oldUpdaterPath = argv[3];
WCHAR secureUpdaterPath[MAX_PATH + 1] = { L'\0' };
result = GetSecureUpdaterPath(secureUpdaterPath); // Does its own logging
if (result) {
LOG(("Passed in path: '%ls'; Using this path for updating: '%ls'.",
oldUpdaterPath, secureUpdaterPath));
DeleteSecureUpdater(secureUpdaterPath);
result = CopyFileW(oldUpdaterPath, secureUpdaterPath, FALSE);
}
if (!result) {
LOG_WARN(("Could not copy path to secure location. (%d)",
GetLastError()));
if (argc > 4 && !WriteStatusFailure(argv[4],
SERVICE_COULD_NOT_COPY_UPDATER)) {
LOG_WARN(("Could not write update.status could not copy updater error"));
}
} else {
// We obtained the path and copied it successfully, update the path to
// use for the service update.
argv[3] = secureUpdaterPath;
WCHAR oldUpdaterINIPath[MAX_PATH + 1] = { L'\0' };
WCHAR secureUpdaterINIPath[MAX_PATH + 1] = { L'\0' };
if (PathGetSiblingFilePath(secureUpdaterINIPath, secureUpdaterPath,
L"updater.ini") &&
PathGetSiblingFilePath(oldUpdaterINIPath, oldUpdaterPath,
L"updater.ini")) {
// This is non fatal if it fails there is no real harm
if (!CopyFileW(oldUpdaterINIPath, secureUpdaterINIPath, FALSE)) {
LOG_WARN(("Could not copy updater.ini from: '%ls' to '%ls'. (%d)",
oldUpdaterINIPath, secureUpdaterINIPath, GetLastError()));
}
}
result = ProcessSoftwareUpdateCommand(argc - 3, argv + 3);
DeleteSecureUpdater(secureUpdaterPath);
}
result = ProcessSoftwareUpdateCommand(argc - 3, argv + 3);
// We might not reach here if the service install succeeded
// because the service self updates itself and the service
// installer will stop the service.

View File

@ -20,8 +20,6 @@ support-files =
[test_basic_form_2pw_2.html]
[test_basic_form_autocomplete.html]
skip-if = toolkit == 'android' # Bug 1258975 on android.
[test_bug_627616.html]
skip-if = toolkit == 'android' # Bug 1258975 on android.
[test_master_password.html]
skip-if = toolkit == 'android' # Bug 1258975 on android.
[test_master_password_cleanup.html]

View File

@ -17,6 +17,8 @@ support-files =
[test_basic_form_html5.html]
[test_basic_form_pwevent.html]
[test_basic_form_pwonly.html]
[test_bug_627616.html]
skip-if = toolkit == 'android' # Bug 1258975 on android.
[test_bug_776171.html]
[test_case_differences.html]
skip-if = toolkit == 'android' # autocomplete

View File

@ -0,0 +1,151 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test bug 627616 related to proxy authentication</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="pwmgr_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
var Ci = SpecialPowers.Ci;
function makeXHR(expectedStatus, expectedText, extra) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "authenticate.sjs?" +
"proxy_user=proxy_user&" +
"proxy_pass=proxy_pass&" +
"proxy_realm=proxy_realm&" +
"user=user1name&" +
"pass=user1pass&" +
"realm=mochirealm&" +
extra || "");
xhr.onloadend = function() {
is(xhr.status, expectedStatus, "xhr.status");
is(xhr.statusText, expectedText, "xhr.statusText");
runNextTest();
}
return xhr;
}
function testNonAnonymousCredentials() {
var xhr = makeXHR(200, "OK");
xhr.send();
}
function testAnonymousCredentials() {
// Test that an anonymous request correctly performs proxy authentication
var xhr = makeXHR(401, "Authentication required");
SpecialPowers.wrap(xhr).channel.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
xhr.send();
}
function testAnonymousNoAuth() {
// Next, test that an anonymous request still does not include any non-proxy
// authentication headers.
var xhr = makeXHR(200, "Authorization header not found", "anonymous=1");
SpecialPowers.wrap(xhr).channel.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
xhr.send();
}
var gExpectedDialogs = 0;
var gCurrentTest;
function runNextTest() {
is(gExpectedDialogs, 0, "received expected number of auth dialogs");
mm.sendAsyncMessage("prepareForNextTest");
mm.addMessageListener("prepareForNextTestDone", function prepared(msg) {
mm.removeMessageListener("prepareForNextTestDone", prepared);
if (pendingTests.length > 0) {
({expectedDialogs: gExpectedDialogs,
test: gCurrentTest} = pendingTests.shift());
gCurrentTest.call(this);
} else {
mm.sendAsyncMessage("cleanup");
mm.addMessageListener("cleanupDone", msg => {
// mm.destroy() is called as a cleanup function by runInParent(), no
// need to do it here.
SimpleTest.finish();
});
}
});
}
var pendingTests = [{expectedDialogs: 2, test: testNonAnonymousCredentials},
{expectedDialogs: 1, test: testAnonymousCredentials},
{expectedDialogs: 0, test: testAnonymousNoAuth}];
let mm = runInParent(() => {
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let channel = Services.io.newChannel2(
"http://example.com",
null,
null,
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_NORMAL,
Ci.nsIContentPolicy.TYPE_OTHER
);
let pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].
getService(Ci.nsIProtocolProxyService);
pps.asyncResolve(channel, 0, {
onProxyAvailable(req, uri, pi, status) {
let mozproxy = "moz-proxy://" + pi.host + ":" + pi.port;
let login = Cc["@mozilla.org/login-manager/loginInfo;1"].
createInstance(Ci.nsILoginInfo);
login.init(mozproxy, null, "proxy_realm", "proxy_user", "proxy_pass",
"", "");
Services.logins.addLogin(login);
let login2 = Cc["@mozilla.org/login-manager/loginInfo;1"].
createInstance(Ci.nsILoginInfo);
login2.init("http://mochi.test:8888", null, "mochirealm", "user1name",
"user1pass", "", "");
Services.logins.addLogin(login2);
//startWatchingForPrompts();
sendAsyncMessage("setupDone");
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolProxyCallback]),
});
addMessageListener("prepareForNextTest", message => {
Cc["@mozilla.org/network/http-auth-manager;1"].
getService(Ci.nsIHttpAuthManager).
clearAll();
sendAsyncMessage("prepareForNextTestDone");
});
let dialogObserverTopic = "common-dialog-loaded";
function dialogObserver(subj, topic, data) {
subj.Dialog.ui.prompt.document.documentElement.acceptDialog();
sendAsyncMessage("promptAccepted");
}
Services.obs.addObserver(dialogObserver, dialogObserverTopic, false);
addMessageListener("cleanup", message => {
Services.obs.removeObserver(dialogObserver, dialogObserverTopic);
sendAsyncMessage("cleanupDone");
});
});
mm.addMessageListener("promptAccepted", msg => {
gExpectedDialogs--;
});
mm.addMessageListener("setupDone", msg => {
runNextTest();
});
</script>
</body>
</html>

View File

@ -1,141 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test bug 627616 related to proxy authentication</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="prompt_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
var Cc = SpecialPowers.Cc;
var Ci = SpecialPowers.Ci;
var systemPrincipal = SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal();
testNum = 1;
var login, login2;
var resolveCallback = SpecialPowers.wrapCallbackObject({
QueryInterface : function (iid) {
const interfaces = [Ci.nsIProtocolProxyCallback, Ci.nsISupports];
if (!interfaces.some( function(v) { return iid.equals(v) } ))
throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
return this;
},
onProxyAvailable : function (req, uri, pi, status) {
init2(SpecialPowers.wrap(pi).host, SpecialPowers.wrap(pi).port);
}
});
function init1() {
var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
var channel = ios.newChannel2("http://example.com",
null,
null,
null, // aLoadingNode
systemPrincipal,
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_NORMAL,
Ci.nsIContentPolicy.TYPE_OTHER);
pps.asyncResolve(channel, 0, resolveCallback);
}
function init2(proxyHost, proxyPort) {
var mozproxy = "moz-proxy://" + proxyHost + ":" + proxyPort;
var pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
login.init(mozproxy, null, "proxy_realm", "proxy_user", "proxy_pass", "", "");
pwmgr.addLogin(login);
login2 = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
login2.init("http://mochi.test:8888", null, "mochirealm", "user1name", "user1pass", "", "");
pwmgr.addLogin(login2);
startCallbackTimer();
}
function cleanup() {
var pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
pwmgr.removeLogin(login);
pwmgr.removeLogin(login2);
timer.cancel();
}
function makeXHR(expectedStatus, expectedText, extra) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "authenticate.sjs?" +
"proxy_user=proxy_user&" +
"proxy_pass=proxy_pass&" +
"proxy_realm=proxy_realm&" +
"user=user1name&" +
"pass=user1pass&" +
"realm=mochirealm&" +
extra || "");
xhr.onloadend = function() {
is(xhr.status, expectedStatus, "xhr.status");
is(xhr.statusText, expectedText, "xhr.statusText");
runNextTest();
}
return xhr;
}
function testNonAnonymousCredentials() {
var xhr = makeXHR(200, "OK");
xhr.send();
}
function testAnonymousCredentials() {
// Test that an anonymous request correctly performs proxy authentication
var xhr = makeXHR(401, "Authentication required");
SpecialPowers.wrap(xhr).channel.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
xhr.send();
}
function testAnonymousNoAuth() {
// Next, test that an anonymous request still does not include any non-proxy
// authentication headers.
var xhr = makeXHR(200, "Authorization header not found", "anonymous=1");
SpecialPowers.wrap(xhr).channel.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
xhr.send();
}
var gExpectedDialogs = 0;
var gCurrentTest;
function runNextTest() {
is(gExpectedDialogs, 0, "received expected number of auth dialogs");
Cc["@mozilla.org/network/http-auth-manager;1"].getService(SpecialPowers.Ci.nsIHttpAuthManager).clearAll();
if (pendingTests.length > 0) {
({expectedDialogs: gExpectedDialogs,
test: gCurrentTest} = pendingTests.shift());
gCurrentTest.call(this);
} else {
cleanup();
SimpleTest.finish();
}
}
var pendingTests = [{expectedDialogs: 2, test: testNonAnonymousCredentials},
{expectedDialogs: 1, test: testAnonymousCredentials},
{expectedDialogs: 0, test: testAnonymousNoAuth}];
init1();
runNextTest();
function handleDialog(doc, testNum)
{
var dialog = doc.getElementById("commonDialog");
dialog.acceptDialog();
gExpectedDialogs--;
startCallbackTimer();
}
</script>
</body>
</html>

View File

@ -5259,13 +5259,6 @@
"description": "Time for a URL bar DB search to return (ms)",
"cpp_guard": "ANDROID"
},
"FENNEC_GECKOAPP_STARTUP_ACTION": {
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 10,
"description": "The way the GeckoApp was launched. (Normal, URL, Prefetch, WebApp, Guest, Restricted, Shortcut)",
"cpp_guard": "ANDROID"
},
"FENNEC_RESTRICTED_PROFILE_RESTRICTIONS": {
"expires_in_version": "50",
"kind": "enumerated",
@ -9320,23 +9313,6 @@
"n_buckets": 10,
"description": "The number of tabs queued when opened."
},
"FENNEC_TABQUEUE_PROMPT_ENABLE_YES" : {
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 3,
"description": "The number of times the tab queue prompt was seen before the user selected YES."
},
"FENNEC_TABQUEUE_PROMPT_ENABLE_NO" : {
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 3,
"description": "The number of times the tab queue prompt was seen before the user selected NO."
},
"FENNEC_TABQUEUE_ENABLED": {
"expires_in_version": "never",
"kind": "boolean",
"description": "Has the tab queue functionality been enabled."
},
"FENNEC_CUSTOM_HOMEPAGE": {
"expires_in_version": "50",
"alert_emails": ["mobile-frontend@mozilla.com"],

View File

@ -125,6 +125,7 @@ var ContentPolicyManager = {
receiveMessage(msg) {
let browser = msg.target instanceof Ci.nsIDOMXULElement ? msg.target : null;
let requestId = RequestId.create();
for (let id of msg.data.ids) {
let callback = this.policies.get(id);
if (!callback) {
@ -134,14 +135,8 @@ var ContentPolicyManager = {
}
let response = null;
let listenerKind = "onStop";
let data = {
url: msg.data.url,
windowId: msg.data.windowId,
parentWindowId: msg.data.parentWindowId,
type: msg.data.type,
browser: browser,
requestId: RequestId.create(),
};
let data = Object.assign({requestId, browser}, msg.data);
delete data.ids;
try {
response = callback(data);
if (response) {
@ -515,32 +510,51 @@ HttpObserverManager = {
kind === "onStart" ||
kind === "onStop";
let commonData = null;
let uri = channel.URI;
for (let [callback, opts] of listeners.entries()) {
if (!this.shouldRunListener(policyType, channel.URI, opts.filter)) {
if (!this.shouldRunListener(policyType, uri, opts.filter)) {
continue;
}
let data = {
requestId: RequestId.get(channel),
url: channel.URI.spec,
method: channel.requestMethod,
browser: browser,
type: WebRequestCommon.typeForPolicyType(policyType),
windowId: loadInfo ? loadInfo.outerWindowID : 0,
parentWindowId: loadInfo ? loadInfo.parentOuterWindowID : 0,
};
if (!commonData) {
commonData = {
requestId: RequestId.get(channel),
url: uri.spec,
method: channel.requestMethod,
browser: browser,
type: WebRequestCommon.typeForPolicyType(policyType),
};
let httpChannel = channel.QueryInterface(Ci.nsIHttpChannelInternal);
try {
data.ip = httpChannel.remoteAddress;
} catch (e) {
// The remoteAddress getter throws if the address is unavailable,
// but ip is an optional property so just ignore the exception.
}
if (loadInfo) {
let originPrincipal = loadInfo.triggeringPrincipal || loadInfo.loadingPrincipal;
if (originPrincipal && originPrincipal.URI) {
commonData.originUrl = originPrincipal.URI.spec;
}
Object.assign(commonData, {
windowId: loadInfo.outerWindowID,
parentWindowId: loadInfo.parentOuterWindowID,
});
} else {
Object.assign(commonData, {
windowId: 0,
parentWindowId: 0,
});
}
if (extraData) {
Object.assign(data, extraData);
if (channel instanceof Ci.nsIHttpChannelInternal) {
try {
commonData.ip = channel.remoteAddress;
} catch (e) {
// The remoteAddress getter throws if the address is unavailable,
// but ip is an optional property so just ignore the exception.
}
}
if (extraData) {
Object.assign(commonData, extraData);
}
}
let data = Object.assign({}, commonData);
if (opts.requestHeaders) {
data.requestHeaders = this.getHeaders(channel, "visitRequestHeaders", kind);
requestHeaderNames = data.requestHeaders.map(h => h.name);

View File

@ -158,7 +158,9 @@ var ContentPolicy = {
type: WebRequestCommon.typeForPolicyType(policyType),
windowId,
parentWindowId};
if (requestOrigin) {
data.originUrl = requestOrigin.spec;
}
if (block) {
let rval = mm.sendSyncMessage("WebRequest:ShouldLoad", data);
if (rval.length == 1 && rval[0].cancel) {

View File

@ -3381,9 +3381,11 @@ this.XPIProvider = {
* @param aManifests
* A dictionary to add new install manifests to to save having to
* reload them later
* @param aAppChanged
* See checkForChanges
* @return true if any new add-ons were installed
*/
installDistributionAddons: function(aManifests) {
installDistributionAddons: function(aManifests, aAppChanged) {
let distroDir;
try {
distroDir = FileUtils.getDir(KEY_APP_DISTRIBUTION, [DIR_EXTENSIONS]);
@ -3429,6 +3431,12 @@ this.XPIProvider = {
continue;
}
/* If this is not an upgrade and we've already handled this extension
* just continue */
if (!aAppChanged && Preferences.isSet(PREF_BRANCH_INSTALLED_ADDON + id)) {
continue;
}
let addon;
try {
addon = syncLoadManifestFromFile(entry, profileLocation);
@ -3560,10 +3568,9 @@ this.XPIProvider = {
}
// If the application has changed then check for new distribution add-ons
if (aAppChanged !== false &&
Preferences.get(PREF_INSTALL_DISTRO_ADDONS, true))
if (Preferences.get(PREF_INSTALL_DISTRO_ADDONS, true))
{
updated = this.installDistributionAddons(manifests);
updated = this.installDistributionAddons(manifests, aAppChanged);
if (updated) {
updateReasons.push("installDistributionAddons");
}

View File

@ -273,17 +273,16 @@ function createAppInfo(ID, name, version, platformVersion="1.0") {
gAppInfo = tmp.getAppInfo();
}
function getManifestURIForBundle(file, manifest="manifest.json") {
function getManifestURIForBundle(file) {
if (file.isDirectory()) {
let path = file.clone();
path.append("install.rdf");
if (path.exists()) {
return NetUtil.newURI(path);
file.append("install.rdf");
if (file.exists()) {
return NetUtil.newURI(file);
}
path.leafName = manifest;
if (path.exists()) {
return NetUtil.newURI(path);
file.leafName = "manifest.json";
if (file.exists()) {
return NetUtil.newURI(file);
}
throw new Error("No manifest file present");
@ -299,8 +298,8 @@ function getManifestURIForBundle(file, manifest="manifest.json") {
return NetUtil.newURI("jar:" + uri.spec + "!/" + "install.rdf");
}
if (zip.hasEntry(manifest)) {
return NetUtil.newURI("jar:" + uri.spec + "!/" + manifest);
if (zip.hasEntry("manifest.json")) {
return NetUtil.newURI("jar:" + uri.spec + "!/" + "manifest.json");
}
throw new Error("No manifest file present");
@ -342,12 +341,8 @@ let getIDForManifest = Task.async(function*(manifestURI) {
return rdfID.QueryInterface(AM_Ci.nsIRDFLiteral).Value;
}
else {
try {
let manifest = JSON.parse(data);
return manifest.applications.gecko.id;
} catch (err) {
return null;
}
let manifest = JSON.parse(data);
return manifest.applications.gecko.id;
}
});
@ -385,15 +380,6 @@ function overrideCertDB(handler) {
let id = yield getIDForManifest(manifestURI);
if (!id) {
manifestURI = getManifestURIForBundle(file, "mozilla.json");
id = yield getIDForManifest(manifestURI);
}
if (!id) {
throw new Error("Cannot find addon ID");
}
// Make sure to close the open zip file or it will be locked.
if (file.isFile()) {
Services.obs.notifyObservers(file, "flush-cache-entry", "cert-override");
@ -1126,7 +1112,7 @@ function writeInstallRDFForExtension(aData, aDir, aId, aExtraFile) {
* An optional string to override the default installation aId
* @return A file pointing to where the extension was installed
*/
function writeWebManifestForExtension(aData, aDir, aId = undefined, aMozData = undefined) {
function writeWebManifestForExtension(aData, aDir, aId = undefined) {
if (!aId)
aId = aData.applications.gecko.id;
@ -1136,26 +1122,19 @@ function writeWebManifestForExtension(aData, aDir, aId = undefined, aMozData = u
if (!dir.exists())
dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
function writeOne(filename, raw) {
let file = dir.clone();
file.append(filename);
if (file.exists())
file.remove(true);
let file = dir.clone();
file.append("manifest.json");
if (file.exists())
file.remove(true);
let data = JSON.stringify(raw);
let fos = AM_Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(AM_Ci.nsIFileOutputStream);
fos.init(file,
FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE,
FileUtils.PERMS_FILE, 0);
fos.write(data, data.length);
fos.close();
}
writeOne("manifest.json", aData);
if (aMozData) {
writeOne("mozilla.json", aMozData);
}
let data = JSON.stringify(aData);
let fos = AM_Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(AM_Ci.nsIFileOutputStream);
fos.init(file,
FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE,
FileUtils.PERMS_FILE, 0);
fos.write(data, data.length);
fos.close();
return dir;
}
@ -1171,13 +1150,6 @@ function writeWebManifestForExtension(aData, aDir, aId = undefined, aMozData = u
zipW.open(file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE);
zipW.addEntryStream("manifest.json", 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE,
stream, false);
if (aMozData) {
let mozStream = AM_Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(AM_Ci.nsIStringInputStream);
mozStream.setData(JSON.stringify(aMozData), -1);
zipW.addEntryStream("mozilla.json", 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE,
mozStream, false);
}
zipW.close();
return file;

View File

@ -159,41 +159,6 @@ add_task(function*() {
yield promiseRestartManager();
});
// applications.gecko.id may be in mozilla.json
add_task(function* test_mozilla_json() {
writeWebManifestForExtension({
name: "Web Extension Name",
version: "1.0",
manifest_version: 2,
}, profileDir, ID, {
applications: {
gecko: {
id: ID
}
}
});
yield promiseRestartManager();
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_eq(addon.version, "1.0");
do_check_eq(addon.name, "Web Extension Name");
do_check_true(addon.isCompatible);
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_false(addon.isSystem);
do_check_eq(addon.type, "extension");
do_check_eq(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_SIGNED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
let file = getFileForAddon(profileDir, ID);
do_check_true(file.exists());
addon.uninstall();
yield promiseRestartManager();
});
add_task(function* test_manifest_localization() {
const ID = "webextension3@tests.mozilla.org";

View File

@ -4663,21 +4663,21 @@ enum {
kE10sForceDisabled = 8,
};
#ifdef XP_WIN
#if defined(XP_WIN) || defined(XP_MACOSX)
const char* kAccessibilityLastRunDatePref = "accessibility.lastLoadDate";
const char* kAccessibilityLoadedLastSessionPref = "accessibility.loadedInLastSession";
#endif // XP_WIN
const char* kForceEnableE10sPref = "browser.tabs.remote.force-enable";
const char* kForceDisableE10sPref = "browser.tabs.remote.force-disable";
#ifdef XP_WIN
static inline uint32_t
PRTimeToSeconds(PRTime t_usec)
{
PRTime usec_per_sec = PR_USEC_PER_SEC;
return uint32_t(t_usec /= usec_per_sec);
}
#endif // XP_WIN
#endif // XP_WIN || XP_MACOSX
const char* kForceEnableE10sPref = "browser.tabs.remote.force-enable";
const char* kForceDisableE10sPref = "browser.tabs.remote.force-disable";
uint32_t
MultiprocessBlockPolicy() {
@ -4704,7 +4704,7 @@ MultiprocessBlockPolicy() {
}
bool disabledForA11y = false;
#ifdef XP_WIN
#if defined(XP_WIN) || defined(XP_MACOSX)
/**
* Avoids enabling e10s if accessibility has recently loaded. Performs the
* following checks:
@ -4731,7 +4731,7 @@ MultiprocessBlockPolicy() {
disabledForA11y = true;
}
}
#endif // XP_WIN
#endif // XP_WIN || XP_MACOSX
if (disabledForA11y) {
gMultiprocessBlockPolicy = kE10sDisabledForAccessibility;

View File

@ -169,7 +169,7 @@ nsBaseWidget::nsBaseWidget()
, mUpdateCursor(true)
, mUseAttachedEvents(false)
, mIMEHasFocus(false)
#ifdef XP_WIN
#if defined(XP_WIN) || defined(XP_MACOSX)
, mAccessibilityInUseFlag(false)
#endif
{
@ -237,7 +237,7 @@ WidgetShutdownObserver::Unregister()
}
}
#ifdef XP_WIN
#if defined(XP_WIN) || defined(XP_MACOSX)
// defined in nsAppRunner.cpp
extern const char* kAccessibilityLastRunDatePref;
@ -259,12 +259,6 @@ nsBaseWidget::Shutdown()
delete sPluginWidgetList;
sPluginWidgetList = nullptr;
}
#if defined(XP_WIN)
if (mAccessibilityInUseFlag) {
uint32_t now = PRTimeToSeconds(PR_Now());
Preferences::SetInt(kAccessibilityLastRunDatePref, now);
}
#endif
#endif
}
@ -1875,8 +1869,12 @@ nsBaseWidget::GetRootAccessible()
nsCOMPtr<nsIAccessibilityService> accService =
services::GetAccessibilityService();
if (accService) {
#ifdef XP_WIN
mAccessibilityInUseFlag = true;
#if defined(XP_WIN) || defined(XP_MACOSX)
if (!mAccessibilityInUseFlag) {
mAccessibilityInUseFlag = true;
uint32_t now = PRTimeToSeconds(PR_Now());
Preferences::SetInt(kAccessibilityLastRunDatePref, now);
}
#endif
return accService->GetRootDocumentAccessible(presShell, nsContentUtils::IsSafeToRunScript());
}

View File

@ -548,7 +548,7 @@ protected:
bool mUpdateCursor;
bool mUseAttachedEvents;
bool mIMEHasFocus;
#ifdef XP_WIN
#if defined(XP_WIN) || defined(XP_MACOSX)
bool mAccessibilityInUseFlag;
#endif
static nsIRollupListener* gRollupListener;