зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound
This commit is contained in:
Коммит
fbc9553a10
|
@ -171,6 +171,7 @@ EXTRA_JS_MODULES.commonjs.diffpatcher.test += [
|
|||
]
|
||||
|
||||
EXTRA_JS_MODULES.commonjs.framescript += [
|
||||
'source/lib/framescript/contextmenu-events.js',
|
||||
'source/lib/framescript/FrameScriptManager.jsm',
|
||||
'source/lib/framescript/LoaderHelper.jsm',
|
||||
'source/lib/framescript/tab-events.js',
|
||||
|
@ -235,6 +236,7 @@ EXTRA_JS_MODULES.commonjs.sdk.console += [
|
|||
EXTRA_JS_MODULES.commonjs.sdk.content += [
|
||||
'source/lib/sdk/content/content-worker.js',
|
||||
'source/lib/sdk/content/content.js',
|
||||
'source/lib/sdk/content/context-menu.js',
|
||||
'source/lib/sdk/content/events.js',
|
||||
'source/lib/sdk/content/loader.js',
|
||||
'source/lib/sdk/content/mod.js',
|
||||
|
|
|
@ -15,11 +15,21 @@ const PATH = __URI__.replace('FrameScriptManager.jsm', '');
|
|||
let loadedTabEvents = false;
|
||||
|
||||
function enableTabEvents() {
|
||||
if (loadedTabEvents)
|
||||
if (loadedTabEvents)
|
||||
return;
|
||||
|
||||
loadedTabEvents = true;
|
||||
globalMM.loadFrameScript(PATH + 'tab-events.js', true);
|
||||
}
|
||||
|
||||
const EXPORTED_SYMBOLS = ['enableTabEvents'];
|
||||
let loadedCMEvents = false;
|
||||
|
||||
function enableCMEvents() {
|
||||
if (loadedCMEvents)
|
||||
return;
|
||||
|
||||
loadedCMEvents = true;
|
||||
globalMM.loadFrameScript(PATH + 'contextmenu-events.js', true);
|
||||
}
|
||||
|
||||
const EXPORTED_SYMBOLS = ['enableTabEvents', 'enableCMEvents'];
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
// Holds remote items for this frame.
|
||||
let keepAlive = new Map();
|
||||
|
||||
// Called to create remote proxies for items. If they already exist we destroy
|
||||
// and recreate. This cna happen if the item changes in some way or in odd
|
||||
// timing cases where the frame script is create around the same time as the
|
||||
// item is created in the main process
|
||||
addMessageListener('sdk/contextmenu/createitems', ({ data: { items, addon }}) => {
|
||||
let { loader } = Cu.import(addon.paths[''] + 'framescript/LoaderHelper.jsm', {});
|
||||
|
||||
for (let itemoptions of items) {
|
||||
let { RemoteItem } = loader(addon).require('sdk/content/context-menu');
|
||||
let item = new RemoteItem(itemoptions, this);
|
||||
|
||||
let oldItem = keepAlive.get(item.id);
|
||||
if (oldItem) {
|
||||
oldItem.destroy();
|
||||
}
|
||||
|
||||
keepAlive.set(item.id, item);
|
||||
}
|
||||
});
|
||||
|
||||
addMessageListener('sdk/contextmenu/destroyitems', ({ data: { items }}) => {
|
||||
for (let id of items) {
|
||||
let item = keepAlive.get(id);
|
||||
item.destroy();
|
||||
keepAlive.delete(id);
|
||||
}
|
||||
});
|
||||
|
||||
sendAsyncMessage('sdk/contextmenu/requestitems');
|
||||
|
||||
Services.obs.addObserver(function(subject, topic, data) {
|
||||
// Many frame scripts run in the same process, check that the context menu
|
||||
// node is in this frame
|
||||
let { event: { target: popupNode }, addonInfo } = subject.wrappedJSObject;
|
||||
if (popupNode.ownerDocument.defaultView.top != content)
|
||||
return;
|
||||
|
||||
for (let item of keepAlive.values()) {
|
||||
item.getContextState(popupNode, addonInfo);
|
||||
}
|
||||
}, "content-contextmenu", false);
|
||||
|
||||
addMessageListener('sdk/contextmenu/activateitems', ({ data: { items, data }, objects: { popupNode }}) => {
|
||||
for (let id of items) {
|
||||
let item = keepAlive.get(id);
|
||||
if (!item)
|
||||
continue;
|
||||
|
||||
item.activate(popupNode, data);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,354 @@
|
|||
/* 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 { Class } = require("../core/heritage");
|
||||
const self = require("../self");
|
||||
const { WorkerChild } = require("./worker-child");
|
||||
const { getInnerId } = require("../window/utils");
|
||||
const { Ci } = require("chrome");
|
||||
const { Services } = require("resource://gre/modules/Services.jsm");
|
||||
|
||||
// These functions are roughly copied from sdk/selection which doesn't work
|
||||
// in the content process
|
||||
function getElementWithSelection(window) {
|
||||
let element = Services.focus.getFocusedElementForWindow(window, false, {});
|
||||
if (!element)
|
||||
return null;
|
||||
|
||||
try {
|
||||
// Accessing selectionStart and selectionEnd on e.g. a button
|
||||
// results in an exception thrown as per the HTML5 spec. See
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection
|
||||
|
||||
let { value, selectionStart, selectionEnd } = element;
|
||||
|
||||
let hasSelection = typeof value === "string" &&
|
||||
!isNaN(selectionStart) &&
|
||||
!isNaN(selectionEnd) &&
|
||||
selectionStart !== selectionEnd;
|
||||
|
||||
return hasSelection ? element : null;
|
||||
}
|
||||
catch (err) {
|
||||
console.exception(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function safeGetRange(selection, rangeNumber) {
|
||||
try {
|
||||
let { rangeCount } = selection;
|
||||
let range = null;
|
||||
|
||||
for (let rangeNumber = 0; rangeNumber < rangeCount; rangeNumber++ ) {
|
||||
range = selection.getRangeAt(rangeNumber);
|
||||
|
||||
if (range && range.toString())
|
||||
break;
|
||||
|
||||
range = null;
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getSelection(window) {
|
||||
let selection = window.getSelection();
|
||||
let range = safeGetRange(selection);
|
||||
if (range)
|
||||
return range.toString();
|
||||
|
||||
let node = getElementWithSelection(window);
|
||||
if (!node)
|
||||
return null;
|
||||
|
||||
return node.value.substring(node.selectionStart, node.selectionEnd);
|
||||
}
|
||||
|
||||
//These are used by PageContext.isCurrent below. If the popupNode or any of
|
||||
//its ancestors is one of these, Firefox uses a tailored context menu, and so
|
||||
//the page context doesn't apply.
|
||||
const NON_PAGE_CONTEXT_ELTS = [
|
||||
Ci.nsIDOMHTMLAnchorElement,
|
||||
Ci.nsIDOMHTMLAppletElement,
|
||||
Ci.nsIDOMHTMLAreaElement,
|
||||
Ci.nsIDOMHTMLButtonElement,
|
||||
Ci.nsIDOMHTMLCanvasElement,
|
||||
Ci.nsIDOMHTMLEmbedElement,
|
||||
Ci.nsIDOMHTMLImageElement,
|
||||
Ci.nsIDOMHTMLInputElement,
|
||||
Ci.nsIDOMHTMLMapElement,
|
||||
Ci.nsIDOMHTMLMediaElement,
|
||||
Ci.nsIDOMHTMLMenuElement,
|
||||
Ci.nsIDOMHTMLObjectElement,
|
||||
Ci.nsIDOMHTMLOptionElement,
|
||||
Ci.nsIDOMHTMLSelectElement,
|
||||
Ci.nsIDOMHTMLTextAreaElement,
|
||||
];
|
||||
|
||||
// List all editable types of inputs. Or is it better to have a list
|
||||
// of non-editable inputs?
|
||||
let editableInputs = {
|
||||
email: true,
|
||||
number: true,
|
||||
password: true,
|
||||
search: true,
|
||||
tel: true,
|
||||
text: true,
|
||||
textarea: true,
|
||||
url: true
|
||||
};
|
||||
|
||||
let CONTEXTS = {};
|
||||
|
||||
let Context = Class({
|
||||
initialize: function(id) {
|
||||
this.id = id;
|
||||
},
|
||||
|
||||
adjustPopupNode: function adjustPopupNode(popupNode) {
|
||||
return popupNode;
|
||||
},
|
||||
|
||||
// Gets state to pass through to the parent process for the node the user
|
||||
// clicked on
|
||||
getState: function(popupNode) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Matches when the context-clicked node doesn't have any of
|
||||
// NON_PAGE_CONTEXT_ELTS in its ancestors
|
||||
CONTEXTS.PageContext = Class({
|
||||
extends: Context,
|
||||
|
||||
getState: function(popupNode) {
|
||||
// If there is a selection in the window then this context does not match
|
||||
if (!popupNode.ownerDocument.defaultView.getSelection().isCollapsed)
|
||||
return false;
|
||||
|
||||
// If the clicked node or any of its ancestors is one of the blacklisted
|
||||
// NON_PAGE_CONTEXT_ELTS then this context does not match
|
||||
while (!(popupNode instanceof Ci.nsIDOMDocument)) {
|
||||
if (NON_PAGE_CONTEXT_ELTS.some(function(type) popupNode instanceof type))
|
||||
return false;
|
||||
|
||||
popupNode = popupNode.parentNode;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// Matches when there is an active selection in the window
|
||||
CONTEXTS.SelectionContext = Class({
|
||||
extends: Context,
|
||||
|
||||
getState: function(popupNode) {
|
||||
if (!popupNode.ownerDocument.defaultView.getSelection().isCollapsed)
|
||||
return true;
|
||||
|
||||
try {
|
||||
// The node may be a text box which has selectionStart and selectionEnd
|
||||
// properties. If not this will throw.
|
||||
let { selectionStart, selectionEnd } = popupNode;
|
||||
return !isNaN(selectionStart) && !isNaN(selectionEnd) &&
|
||||
selectionStart !== selectionEnd;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Matches when the context-clicked node or any of its ancestors matches the
|
||||
// selector given
|
||||
CONTEXTS.SelectorContext = Class({
|
||||
extends: Context,
|
||||
|
||||
initialize: function initialize(id, selector) {
|
||||
Context.prototype.initialize.call(this, id);
|
||||
this.selector = selector;
|
||||
},
|
||||
|
||||
adjustPopupNode: function adjustPopupNode(popupNode) {
|
||||
let selector = this.selector;
|
||||
|
||||
while (!(popupNode instanceof Ci.nsIDOMDocument)) {
|
||||
if (popupNode.mozMatchesSelector(selector))
|
||||
return popupNode;
|
||||
|
||||
popupNode = popupNode.parentNode;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
getState: function(popupNode) {
|
||||
return !!this.adjustPopupNode(popupNode);
|
||||
}
|
||||
});
|
||||
|
||||
// Matches when the page url matches any of the patterns given
|
||||
CONTEXTS.URLContext = Class({
|
||||
extends: Context,
|
||||
|
||||
getState: function(popupNode) {
|
||||
return popupNode.ownerDocument.URL;
|
||||
}
|
||||
});
|
||||
|
||||
// Matches when the user-supplied predicate returns true
|
||||
CONTEXTS.PredicateContext = Class({
|
||||
extends: Context,
|
||||
|
||||
getState: function(node) {
|
||||
let window = node.ownerDocument.defaultView;
|
||||
let data = {};
|
||||
|
||||
data.documentType = node.ownerDocument.contentType;
|
||||
|
||||
data.documentURL = node.ownerDocument.location.href;
|
||||
data.targetName = node.nodeName.toLowerCase();
|
||||
data.targetID = node.id || null ;
|
||||
|
||||
if ((data.targetName === 'input' && editableInputs[node.type]) ||
|
||||
data.targetName === 'textarea') {
|
||||
data.isEditable = !node.readOnly && !node.disabled;
|
||||
}
|
||||
else {
|
||||
data.isEditable = node.isContentEditable;
|
||||
}
|
||||
|
||||
data.selectionText = getSelection(window, "TEXT");
|
||||
|
||||
data.srcURL = node.src || null;
|
||||
data.value = node.value || null;
|
||||
|
||||
while (!data.linkURL && node) {
|
||||
data.linkURL = node.href || null;
|
||||
node = node.parentNode;
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
function instantiateContext({ id, type, args }) {
|
||||
if (!(type in CONTEXTS)) {
|
||||
console.error("Attempt to use unknown context " + type);
|
||||
return;
|
||||
}
|
||||
return new CONTEXTS[type](id, ...args);
|
||||
}
|
||||
|
||||
let ContextWorker = Class({
|
||||
implements: [ WorkerChild ],
|
||||
|
||||
// Calls the context workers context listeners and returns the first result
|
||||
// that is either a string or a value that evaluates to true. If all of the
|
||||
// listeners returned false then returns false. If there are no listeners,
|
||||
// returns true (show the menu item by default).
|
||||
getMatchedContext: function getCurrentContexts(popupNode) {
|
||||
let results = this.sandbox.emitSync("context", popupNode);
|
||||
if (!results.length)
|
||||
return true;
|
||||
return results.reduce((val, result) => val || result);
|
||||
},
|
||||
|
||||
// Emits a click event in the worker's port. popupNode is the node that was
|
||||
// context-clicked, and clickedItemData is the data of the item that was
|
||||
// clicked.
|
||||
fireClick: function fireClick(popupNode, clickedItemData) {
|
||||
this.sandbox.emitSync("click", popupNode, clickedItemData);
|
||||
}
|
||||
});
|
||||
|
||||
// Gets the item's content script worker for a window, creating one if necessary
|
||||
// Once created it will be automatically destroyed when the window unloads.
|
||||
// If there is not content scripts for the item then null will be returned.
|
||||
function getItemWorkerForWindow(item, window) {
|
||||
if (!item.contentScript && !item.contentScriptFile)
|
||||
return null;
|
||||
|
||||
let id = getInnerId(window);
|
||||
let worker = item.workerMap.get(id);
|
||||
|
||||
if (worker)
|
||||
return worker;
|
||||
|
||||
worker = ContextWorker({
|
||||
id: item.id,
|
||||
window: id,
|
||||
manager: item.manager,
|
||||
contentScript: item.contentScript,
|
||||
contentScriptFile: item.contentScriptFile,
|
||||
onDetach: function() {
|
||||
item.workerMap.delete(id);
|
||||
}
|
||||
});
|
||||
|
||||
item.workerMap.set(id, worker);
|
||||
|
||||
return worker;
|
||||
}
|
||||
|
||||
// A very simple remote proxy for every item. It's job is to provide data for
|
||||
// the main process to use to determine visibility state and to call into
|
||||
// content scripts when clicked.
|
||||
let RemoteItem = Class({
|
||||
initialize: function(options, manager) {
|
||||
this.id = options.id;
|
||||
this.contexts = [instantiateContext(c) for (c of options.contexts)];
|
||||
this.contentScript = options.contentScript;
|
||||
this.contentScriptFile = options.contentScriptFile;
|
||||
|
||||
this.manager = manager;
|
||||
|
||||
this.workerMap = new Map();
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
for (let worker of this.workerMap.values()) {
|
||||
worker.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
activate: function(popupNode, data) {
|
||||
let worker = getItemWorkerForWindow(this, popupNode.ownerDocument.defaultView);
|
||||
if (!worker)
|
||||
return;
|
||||
|
||||
for (let context of this.contexts)
|
||||
popupNode = context.adjustPopupNode(popupNode);
|
||||
|
||||
worker.fireClick(popupNode, data);
|
||||
},
|
||||
|
||||
// Fills addonInfo with state data to send through to the main process
|
||||
getContextState: function(popupNode, addonInfo) {
|
||||
if (!(self.id in addonInfo))
|
||||
addonInfo[self.id] = {};
|
||||
|
||||
let worker = getItemWorkerForWindow(this, popupNode.ownerDocument.defaultView);
|
||||
let contextStates = {};
|
||||
for (let context of this.contexts)
|
||||
contextStates[context.id] = context.getState(popupNode);
|
||||
|
||||
addonInfo[self.id][this.id] = {
|
||||
// It isn't ideal to create a PageContext for every item but there isn't
|
||||
// a good shared place to do it.
|
||||
pageContext: (new CONTEXTS.PageContext()).getState(popupNode),
|
||||
contextStates,
|
||||
hasWorker: !!worker,
|
||||
workerContext: worker ? worker.getMatchedContext(popupNode) : true
|
||||
}
|
||||
}
|
||||
});
|
||||
exports.RemoteItem = RemoteItem;
|
|
@ -19,14 +19,20 @@ const { validateOptions, getTypeOf } = require("./deprecated/api-utils");
|
|||
const { URL, isValidURI } = require("./url");
|
||||
const { WindowTracker, browserWindowIterator } = require("./deprecated/window-utils");
|
||||
const { isBrowser, getInnerId } = require("./window/utils");
|
||||
const { Ci } = require("chrome");
|
||||
const { Ci, Cc, Cu } = require("chrome");
|
||||
const { MatchPattern } = require("./util/match-pattern");
|
||||
const { Worker } = require("./content/worker");
|
||||
const { EventTarget } = require("./event/target");
|
||||
const { emit } = require('./event/core');
|
||||
const { when } = require('./system/unload');
|
||||
const selection = require('./selection');
|
||||
const { contract: loaderContract } = require('./content/loader');
|
||||
const { omit } = require('./util/object');
|
||||
const self = require('./self')
|
||||
|
||||
// null-out cycles in .modules to make @loader/options JSONable
|
||||
const ADDON = omit(require('@loader/options'), ['modules', 'globals']);
|
||||
|
||||
require('../framescript/FrameScriptManager.jsm').enableCMEvents();
|
||||
|
||||
// All user items we add have this class.
|
||||
const ITEM_CLASS = "addon-context-menu-item";
|
||||
|
@ -59,30 +65,13 @@ const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu";
|
|||
// The class of the overflow submenu's xul:menupopup.
|
||||
const OVERFLOW_POPUP_CLASS = "addon-content-menu-overflow-popup";
|
||||
|
||||
//These are used by PageContext.isCurrent below. If the popupNode or any of
|
||||
//its ancestors is one of these, Firefox uses a tailored context menu, and so
|
||||
//the page context doesn't apply.
|
||||
const NON_PAGE_CONTEXT_ELTS = [
|
||||
Ci.nsIDOMHTMLAnchorElement,
|
||||
Ci.nsIDOMHTMLAppletElement,
|
||||
Ci.nsIDOMHTMLAreaElement,
|
||||
Ci.nsIDOMHTMLButtonElement,
|
||||
Ci.nsIDOMHTMLCanvasElement,
|
||||
Ci.nsIDOMHTMLEmbedElement,
|
||||
Ci.nsIDOMHTMLImageElement,
|
||||
Ci.nsIDOMHTMLInputElement,
|
||||
Ci.nsIDOMHTMLMapElement,
|
||||
Ci.nsIDOMHTMLMediaElement,
|
||||
Ci.nsIDOMHTMLMenuElement,
|
||||
Ci.nsIDOMHTMLObjectElement,
|
||||
Ci.nsIDOMHTMLOptionElement,
|
||||
Ci.nsIDOMHTMLSelectElement,
|
||||
Ci.nsIDOMHTMLTextAreaElement,
|
||||
];
|
||||
|
||||
// Holds private properties for API objects
|
||||
let internal = ns();
|
||||
|
||||
function uuid() {
|
||||
return require('./util/uuid').uuid().toString();
|
||||
}
|
||||
|
||||
function getScheme(spec) {
|
||||
try {
|
||||
return URL(spec).scheme;
|
||||
|
@ -92,15 +81,22 @@ function getScheme(spec) {
|
|||
}
|
||||
}
|
||||
|
||||
let MessageManager = Cc["@mozilla.org/globalmessagemanager;1"].
|
||||
getService(Ci.nsIMessageBroadcaster);
|
||||
|
||||
let Context = Class({
|
||||
initialize: function() {
|
||||
internal(this).id = uuid();
|
||||
},
|
||||
|
||||
// Returns the node that made this context current
|
||||
adjustPopupNode: function adjustPopupNode(popupNode) {
|
||||
return popupNode;
|
||||
},
|
||||
|
||||
// Returns whether this context is current for the current node
|
||||
isCurrent: function isCurrent(popupNode) {
|
||||
return false;
|
||||
isCurrent: function isCurrent(state) {
|
||||
return state;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -109,21 +105,12 @@ let Context = Class({
|
|||
let PageContext = Class({
|
||||
extends: Context,
|
||||
|
||||
isCurrent: function isCurrent(popupNode) {
|
||||
// If there is a selection in the window then this context does not match
|
||||
if (!popupNode.ownerDocument.defaultView.getSelection().isCollapsed)
|
||||
return false;
|
||||
|
||||
// If the clicked node or any of its ancestors is one of the blacklisted
|
||||
// NON_PAGE_CONTEXT_ELTS then this context does not match
|
||||
while (!(popupNode instanceof Ci.nsIDOMDocument)) {
|
||||
if (NON_PAGE_CONTEXT_ELTS.some(function(type) popupNode instanceof type))
|
||||
return false;
|
||||
|
||||
popupNode = popupNode.parentNode;
|
||||
serialize: function() {
|
||||
return {
|
||||
id: internal(this).id,
|
||||
type: "PageContext",
|
||||
args: []
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
exports.PageContext = PageContext;
|
||||
|
@ -132,19 +119,11 @@ exports.PageContext = PageContext;
|
|||
let SelectionContext = Class({
|
||||
extends: Context,
|
||||
|
||||
isCurrent: function isCurrent(popupNode) {
|
||||
if (!popupNode.ownerDocument.defaultView.getSelection().isCollapsed)
|
||||
return true;
|
||||
|
||||
try {
|
||||
// The node may be a text box which has selectionStart and selectionEnd
|
||||
// properties. If not this will throw.
|
||||
let { selectionStart, selectionEnd } = popupNode;
|
||||
return !isNaN(selectionStart) && !isNaN(selectionEnd) &&
|
||||
selectionStart !== selectionEnd;
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
serialize: function() {
|
||||
return {
|
||||
id: internal(this).id,
|
||||
type: "SelectionContext",
|
||||
args: []
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -156,6 +135,7 @@ let SelectorContext = Class({
|
|||
extends: Context,
|
||||
|
||||
initialize: function initialize(selector) {
|
||||
Context.prototype.initialize.call(this);
|
||||
let options = validateOptions({ selector: selector }, {
|
||||
selector: {
|
||||
is: ["string"],
|
||||
|
@ -165,21 +145,12 @@ let SelectorContext = Class({
|
|||
internal(this).selector = options.selector;
|
||||
},
|
||||
|
||||
adjustPopupNode: function adjustPopupNode(popupNode) {
|
||||
let selector = internal(this).selector;
|
||||
|
||||
while (!(popupNode instanceof Ci.nsIDOMDocument)) {
|
||||
if (popupNode.mozMatchesSelector(selector))
|
||||
return popupNode;
|
||||
|
||||
popupNode = popupNode.parentNode;
|
||||
serialize: function() {
|
||||
return {
|
||||
id: internal(this).id,
|
||||
type: "SelectorContext",
|
||||
args: [internal(this).selector]
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
isCurrent: function isCurrent(popupNode) {
|
||||
return !!this.adjustPopupNode(popupNode);
|
||||
}
|
||||
});
|
||||
exports.SelectorContext = SelectorContext;
|
||||
|
@ -189,6 +160,7 @@ let URLContext = Class({
|
|||
extends: Context,
|
||||
|
||||
initialize: function initialize(patterns) {
|
||||
Context.prototype.initialize.call(this);
|
||||
patterns = Array.isArray(patterns) ? patterns : [patterns];
|
||||
|
||||
try {
|
||||
|
@ -198,12 +170,18 @@ let URLContext = Class({
|
|||
throw new Error("Patterns must be a string, regexp or an array of " +
|
||||
"strings or regexps: " + err);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
isCurrent: function isCurrent(popupNode) {
|
||||
let url = popupNode.ownerDocument.URL;
|
||||
isCurrent: function isCurrent(url) {
|
||||
return internal(this).patterns.some(function (p) p.test(url));
|
||||
},
|
||||
|
||||
serialize: function() {
|
||||
return {
|
||||
id: internal(this).id,
|
||||
type: "URLContext",
|
||||
args: []
|
||||
}
|
||||
}
|
||||
});
|
||||
exports.URLContext = URLContext;
|
||||
|
@ -213,6 +191,7 @@ let PredicateContext = Class({
|
|||
extends: Context,
|
||||
|
||||
initialize: function initialize(predicate) {
|
||||
Context.prototype.initialize.call(this);
|
||||
let options = validateOptions({ predicate: predicate }, {
|
||||
predicate: {
|
||||
is: ["function"],
|
||||
|
@ -222,56 +201,20 @@ let PredicateContext = Class({
|
|||
internal(this).predicate = options.predicate;
|
||||
},
|
||||
|
||||
isCurrent: function isCurrent(popupNode) {
|
||||
return internal(this).predicate(populateCallbackNodeData(popupNode));
|
||||
isCurrent: function isCurrent(state) {
|
||||
return internal(this).predicate(state);
|
||||
},
|
||||
|
||||
serialize: function() {
|
||||
return {
|
||||
id: internal(this).id,
|
||||
type: "PredicateContext",
|
||||
args: []
|
||||
}
|
||||
}
|
||||
});
|
||||
exports.PredicateContext = PredicateContext;
|
||||
|
||||
// List all editable types of inputs. Or is it better to have a list
|
||||
// of non-editable inputs?
|
||||
let editableInputs = {
|
||||
email: true,
|
||||
number: true,
|
||||
password: true,
|
||||
search: true,
|
||||
tel: true,
|
||||
text: true,
|
||||
textarea: true,
|
||||
url: true
|
||||
};
|
||||
|
||||
function populateCallbackNodeData(node) {
|
||||
let window = node.ownerDocument.defaultView;
|
||||
let data = {};
|
||||
|
||||
data.documentType = node.ownerDocument.contentType;
|
||||
|
||||
data.documentURL = node.ownerDocument.location.href;
|
||||
data.targetName = node.nodeName.toLowerCase();
|
||||
data.targetID = node.id || null ;
|
||||
|
||||
if ((data.targetName === 'input' && editableInputs[node.type]) ||
|
||||
data.targetName === 'textarea') {
|
||||
data.isEditable = !node.readOnly && !node.disabled;
|
||||
}
|
||||
else {
|
||||
data.isEditable = node.isContentEditable;
|
||||
}
|
||||
|
||||
data.selectionText = selection.text;
|
||||
|
||||
data.srcURL = node.src || null;
|
||||
data.value = node.value || null;
|
||||
|
||||
while (!data.linkURL && node) {
|
||||
data.linkURL = node.href || null;
|
||||
node = node.parentNode;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function removeItemFromArray(array, item) {
|
||||
return array.filter(function(i) i !== item);
|
||||
}
|
||||
|
@ -362,130 +305,82 @@ let menuRules = mix(labelledItemRules, {
|
|||
}
|
||||
});
|
||||
|
||||
let ContextWorker = Class({
|
||||
implements: [ Worker ],
|
||||
|
||||
// Calls the context workers context listeners and returns the first result
|
||||
// that is either a string or a value that evaluates to true. If all of the
|
||||
// listeners returned false then returns false. If there are no listeners,
|
||||
// returns true (show the menu item by default).
|
||||
getMatchedContext: function getCurrentContexts(popupNode) {
|
||||
let results = this.getSandbox().emitSync("context", popupNode);
|
||||
if (!results.length)
|
||||
return true;
|
||||
return results.reduce((val, result) => val || result);
|
||||
},
|
||||
|
||||
// Emits a click event in the worker's port. popupNode is the node that was
|
||||
// context-clicked, and clickedItemData is the data of the item that was
|
||||
// clicked.
|
||||
fireClick: function fireClick(popupNode, clickedItemData) {
|
||||
this.getSandbox().emitSync("click", popupNode, clickedItemData);
|
||||
}
|
||||
});
|
||||
|
||||
// Returns true if any contexts match. If there are no contexts then a
|
||||
// PageContext is tested instead
|
||||
function hasMatchingContext(contexts, popupNode) {
|
||||
for (let context in contexts) {
|
||||
if (!context.isCurrent(popupNode))
|
||||
function hasMatchingContext(contexts, addonInfo) {
|
||||
for (let context of contexts) {
|
||||
if (!(internal(context).id in addonInfo.contextStates)) {
|
||||
console.error("Missing state for context " + internal(context).id + " this is an error in the SDK modules.");
|
||||
return false;
|
||||
}
|
||||
if (!context.isCurrent(addonInfo.contextStates[internal(context).id]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Gets the matched context from any worker for this item. If there is no worker
|
||||
// or no matched context then returns false.
|
||||
function getCurrentWorkerContext(item, popupNode) {
|
||||
let worker = getItemWorkerForWindow(item, popupNode.ownerDocument.defaultView);
|
||||
if (!worker)
|
||||
return true;
|
||||
return worker.getMatchedContext(popupNode);
|
||||
}
|
||||
|
||||
// Tests whether an item should be visible or not based on its contexts and
|
||||
// content scripts
|
||||
function isItemVisible(item, popupNode, defaultVisibility) {
|
||||
function isItemVisible(item, addonInfo, usePageWorker) {
|
||||
if (!item.context.length) {
|
||||
let worker = getItemWorkerForWindow(item, popupNode.ownerDocument.defaultView);
|
||||
if (!worker)
|
||||
return defaultVisibility;
|
||||
if (!addonInfo.hasWorker)
|
||||
return usePageWorker ? addonInfo.pageContext : true;
|
||||
}
|
||||
|
||||
if (!hasMatchingContext(item.context, popupNode))
|
||||
if (!hasMatchingContext(item.context, addonInfo))
|
||||
return false;
|
||||
|
||||
let context = getCurrentWorkerContext(item, popupNode);
|
||||
let context = addonInfo.workerContext;
|
||||
if (typeof(context) === "string" && context != "")
|
||||
item.label = context;
|
||||
|
||||
return !!context;
|
||||
}
|
||||
|
||||
// Gets the item's content script worker for a window, creating one if necessary
|
||||
// Once created it will be automatically destroyed when the window unloads.
|
||||
// If there is not content scripts for the item then null will be returned.
|
||||
function getItemWorkerForWindow(item, window) {
|
||||
if (!item.contentScript && !item.contentScriptFile)
|
||||
return null;
|
||||
|
||||
let id = getInnerId(window);
|
||||
let worker = internal(item).workerMap.get(id);
|
||||
|
||||
if (worker)
|
||||
return worker;
|
||||
|
||||
worker = ContextWorker({
|
||||
window: window,
|
||||
contentScript: item.contentScript,
|
||||
contentScriptFile: item.contentScriptFile,
|
||||
onMessage: function(msg) {
|
||||
emit(item, "message", msg);
|
||||
},
|
||||
onDetach: function() {
|
||||
internal(item).workerMap.delete(id);
|
||||
}
|
||||
});
|
||||
|
||||
internal(item).workerMap.set(id, worker);
|
||||
|
||||
return worker;
|
||||
}
|
||||
|
||||
// Called when an item is clicked to send out click events to the content
|
||||
// scripts
|
||||
function itemActivated(item, clickedItem, popupNode) {
|
||||
let worker = getItemWorkerForWindow(item, popupNode.ownerDocument.defaultView);
|
||||
|
||||
if (worker) {
|
||||
let adjustedNode = popupNode;
|
||||
for (let context in item.context)
|
||||
adjustedNode = context.adjustPopupNode(adjustedNode);
|
||||
worker.fireClick(adjustedNode, clickedItem.data);
|
||||
function itemActivated(item, clickedNode) {
|
||||
let data = {
|
||||
items: [internal(item).id],
|
||||
data: item.data,
|
||||
}
|
||||
|
||||
if (item.parentMenu)
|
||||
itemActivated(item.parentMenu, clickedItem, popupNode);
|
||||
while (item.parentMenu) {
|
||||
item = item.parentMenu;
|
||||
data.items.push(internal(item).id);
|
||||
}
|
||||
|
||||
let menuData = clickedNode.ownerDocument.defaultView.gContextMenuContentData;
|
||||
let messageManager = menuData.browser.messageManager;
|
||||
messageManager.sendAsyncMessage('sdk/contextmenu/activateitems', data, {
|
||||
popupNode: menuData.popupNode
|
||||
});
|
||||
}
|
||||
|
||||
function serializeItem(item) {
|
||||
return {
|
||||
id: internal(item).id,
|
||||
contexts: [c.serialize() for (c of item.context)],
|
||||
contentScript: item.contentScript,
|
||||
contentScriptFile: item.contentScriptFile,
|
||||
};
|
||||
}
|
||||
|
||||
// All things that appear in the context menu extend this
|
||||
let BaseItem = Class({
|
||||
initialize: function initialize() {
|
||||
addCollectionProperty(this, "context");
|
||||
|
||||
// Used to cache content script workers and the windows they have been
|
||||
// created for
|
||||
internal(this).workerMap = new Map();
|
||||
internal(this).id = uuid();
|
||||
|
||||
internal(this).contexts = [];
|
||||
if ("context" in internal(this).options && internal(this).options.context) {
|
||||
let contexts = internal(this).options.context;
|
||||
if (Array.isArray(contexts)) {
|
||||
for (let context of contexts)
|
||||
this.context.add(context);
|
||||
internal(this).contexts.push(context);
|
||||
}
|
||||
else {
|
||||
this.context.add(contexts);
|
||||
internal(this).contexts.push(contexts);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -500,15 +395,59 @@ let BaseItem = Class({
|
|||
value: internal(this).options.contentScript
|
||||
});
|
||||
|
||||
// Resolve URIs here as tests may have overriden self
|
||||
let files = internal(this).options.contentScriptFile;
|
||||
if (files) {
|
||||
if (!Array.isArray(files))
|
||||
files = [files];
|
||||
files = files.map(self.data.url);
|
||||
}
|
||||
internal(this).options.contentScriptFile = files;
|
||||
Object.defineProperty(this, "contentScriptFile", {
|
||||
enumerable: true,
|
||||
value: internal(this).options.contentScriptFile
|
||||
});
|
||||
|
||||
// Notify all frames of this new item
|
||||
sendItems([serializeItem(this)]);
|
||||
},
|
||||
|
||||
destroy: function destroy() {
|
||||
if (internal(this).destroyed)
|
||||
return;
|
||||
|
||||
// Tell all existing frames that this item has been destroyed
|
||||
MessageManager.broadcastAsyncMessage("sdk/contextmenu/destroyitems", {
|
||||
items: [internal(this).id]
|
||||
});
|
||||
|
||||
if (this.parentMenu)
|
||||
this.parentMenu.removeItem(this);
|
||||
|
||||
internal(this).destroyed = true;
|
||||
},
|
||||
|
||||
get context() {
|
||||
let contexts = internal(this).contexts.slice(0);
|
||||
contexts.add = (context) => {
|
||||
internal(this).contexts.push(context);
|
||||
// Notify all frames that this item has changed
|
||||
sendItems([serializeItem(this)]);
|
||||
};
|
||||
contexts.remove = (context) => {
|
||||
internal(this).contexts = internal(this).contexts.filter(c => {
|
||||
return c != context;
|
||||
});
|
||||
// Notify all frames that this item has changed
|
||||
sendItems([serializeItem(this)]);
|
||||
};
|
||||
return contexts;
|
||||
},
|
||||
|
||||
set context(val) {
|
||||
internal(this).contexts = val.slice(0);
|
||||
// Notify all frames that this item has changed
|
||||
sendItems([serializeItem(this)]);
|
||||
},
|
||||
|
||||
get parentMenu() {
|
||||
|
@ -516,6 +455,13 @@ let BaseItem = Class({
|
|||
},
|
||||
});
|
||||
|
||||
function workerMessageReceived({ data: { id, args } }) {
|
||||
if (internal(this).id != id)
|
||||
return;
|
||||
|
||||
emit(this, ...args);
|
||||
}
|
||||
|
||||
// All things that have a label on the context menu extend this
|
||||
let LabelledItem = Class({
|
||||
extends: BaseItem,
|
||||
|
@ -524,11 +470,16 @@ let LabelledItem = Class({
|
|||
initialize: function initialize(options) {
|
||||
BaseItem.prototype.initialize.call(this);
|
||||
EventTarget.prototype.initialize.call(this, options);
|
||||
|
||||
internal(this).messageListener = workerMessageReceived.bind(this);
|
||||
MessageManager.addMessageListener('sdk/worker/event', internal(this).messageListener);
|
||||
},
|
||||
|
||||
destroy: function destroy() {
|
||||
for (let [,worker] of internal(this).workerMap)
|
||||
worker.destroy();
|
||||
if (internal(this).destroyed)
|
||||
return;
|
||||
|
||||
MessageManager.removeMessageListener('sdk/worker/event', internal(this).messageListener);
|
||||
|
||||
BaseItem.prototype.destroy.call(this);
|
||||
},
|
||||
|
@ -712,7 +663,39 @@ exports.Separator = Separator;
|
|||
let contentContextMenu = ItemContainer();
|
||||
exports.contentContextMenu = contentContextMenu;
|
||||
|
||||
function getContainerItems(container) {
|
||||
let items = [];
|
||||
for (let item of internal(container).children) {
|
||||
items.push(serializeItem(item));
|
||||
if (item instanceof Menu)
|
||||
items = items.concat(getContainerItems(item));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
// Notify all frames of these new or changed items
|
||||
function sendItems(items) {
|
||||
MessageManager.broadcastAsyncMessage("sdk/contextmenu/createitems", {
|
||||
items,
|
||||
addon: ADDON,
|
||||
});
|
||||
}
|
||||
|
||||
// Called when a new frame is created and wants to get the current list of items
|
||||
function remoteItemRequest({ target: { messageManager } }) {
|
||||
let items = getContainerItems(contentContextMenu);
|
||||
if (items.length == 0)
|
||||
return;
|
||||
|
||||
messageManager.sendAsyncMessage("sdk/contextmenu/createitems", {
|
||||
items,
|
||||
addon: ADDON,
|
||||
});
|
||||
}
|
||||
MessageManager.addMessageListener('sdk/contextmenu/requestitems', remoteItemRequest);
|
||||
|
||||
when(function() {
|
||||
MessageManager.removeMessageListener('sdk/contextmenu/requestitems', remoteItemRequest);
|
||||
contentContextMenu.destroy();
|
||||
});
|
||||
|
||||
|
@ -800,16 +783,16 @@ let MenuWrapper = Class({
|
|||
|
||||
// Recurses through the menu setting the visibility of items. Returns true
|
||||
// if any of the items in this menu were visible
|
||||
setVisibility: function setVisibility(menu, popupNode, defaultVisibility) {
|
||||
setVisibility: function setVisibility(menu, addonInfo, usePageWorker) {
|
||||
let anyVisible = false;
|
||||
|
||||
for (let item of internal(menu).children) {
|
||||
let visible = isItemVisible(item, popupNode, defaultVisibility);
|
||||
let visible = isItemVisible(item, addonInfo[internal(item).id], usePageWorker);
|
||||
|
||||
// Recurse through Menus, if none of the sub-items were visible then the
|
||||
// menu is hidden too.
|
||||
if (visible && (item instanceof Menu))
|
||||
visible = this.setVisibility(item, popupNode, true);
|
||||
visible = this.setVisibility(item, addonInfo, false);
|
||||
|
||||
let xulNode = this.getXULNodeForItem(item);
|
||||
xulNode.hidden = !visible;
|
||||
|
@ -912,7 +895,7 @@ let MenuWrapper = Class({
|
|||
if (event.target !== xulNode)
|
||||
return;
|
||||
|
||||
itemActivated(item, item, self.contextMenu.triggerNode);
|
||||
itemActivated(item, xulNode);
|
||||
}, false);
|
||||
}
|
||||
|
||||
|
@ -1027,8 +1010,14 @@ let MenuWrapper = Class({
|
|||
this.populate(this.items);
|
||||
}
|
||||
|
||||
let popupNode = event.target.triggerNode;
|
||||
this.setVisibility(this.items, popupNode, PageContext().isCurrent(popupNode));
|
||||
let mainWindow = event.target.ownerDocument.defaultView;
|
||||
this.contextMenuContentData = mainWindow.gContextMenuContentData
|
||||
let addonInfo = this.contextMenuContentData.addonInfo[self.id];
|
||||
if (!addonInfo) {
|
||||
console.warn("No context menu state data was provided.");
|
||||
return;
|
||||
}
|
||||
this.setVisibility(this.items, addonInfo, true);
|
||||
}
|
||||
catch (e) {
|
||||
console.exception(e);
|
||||
|
|
|
@ -11,6 +11,7 @@ const { Loader } = require('sdk/test/loader');
|
|||
const timer = require("sdk/timers");
|
||||
const { merge } = require("sdk/util/object");
|
||||
const { defer } = require("sdk/core/promise");
|
||||
const observers = require("sdk/system/events");
|
||||
|
||||
// These should match the same constants in the module.
|
||||
const ITEM_CLASS = "addon-context-menu-item";
|
||||
|
@ -103,7 +104,7 @@ exports.testSelectorContextMatch = function (assert, done) {
|
|||
});
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("image"), function (popup) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu([item], [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -125,7 +126,7 @@ exports.testSelectorAncestorContextMatch = function (assert, done) {
|
|||
});
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("span-link"), function (popup) {
|
||||
test.showMenu("#span-link", function (popup) {
|
||||
test.checkMenu([item], [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -209,7 +210,7 @@ exports.testPageContextNoMatch = function (assert, done) {
|
|||
];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("image"), function (popup) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu(items, items, []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -249,9 +250,8 @@ exports.testSelectionContextMatchInTextField = function (assert, done) {
|
|||
});
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
let textfield = doc.getElementById("textfield");
|
||||
textfield.setSelectionRange(0, textfield.value.length);
|
||||
test.showMenu(textfield, function (popup) {
|
||||
test.selectRange("#textfield", 0, null);
|
||||
test.showMenu("#textfield", function (popup) {
|
||||
test.checkMenu([item], [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -271,9 +271,8 @@ exports.testSelectionContextNoMatchInTextField = function (assert, done) {
|
|||
});
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
let textfield = doc.getElementById("textfield");
|
||||
textfield.setSelectionRange(0, 0);
|
||||
test.showMenu(textfield, function (popup) {
|
||||
test.selectRange("#textfield", 0, 0);
|
||||
test.showMenu("#textfield", function (popup) {
|
||||
test.checkMenu([item], [item], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -314,25 +313,31 @@ exports.testSelectionContextInNewTab = function (assert, done) {
|
|||
let link = doc.getElementById("targetlink");
|
||||
link.click();
|
||||
|
||||
test.delayedEventListener(this.tabBrowser, "load", function () {
|
||||
let browser = test.tabBrowser.selectedBrowser;
|
||||
let window = browser.contentWindow;
|
||||
let doc = browser.contentDocument;
|
||||
window.getSelection().selectAllChildren(doc.body);
|
||||
|
||||
test.showMenu(null, function (popup) {
|
||||
test.checkMenu([item], [], []);
|
||||
popup.hidePopup();
|
||||
|
||||
test.tabBrowser.removeTab(test.tabBrowser.selectedTab);
|
||||
test.tabBrowser.selectedTab = test.tab;
|
||||
let tablistener = event => {
|
||||
this.tabBrowser.tabContainer.removeEventListener("TabOpen", tablistener, false);
|
||||
let tab = event.target;
|
||||
let browser = tab.linkedBrowser;
|
||||
this.loadFrameScript(browser);
|
||||
this.delayedEventListener(browser, "load", () => {
|
||||
let window = browser.contentWindow;
|
||||
let doc = browser.contentDocument;
|
||||
window.getSelection().selectAllChildren(doc.body);
|
||||
|
||||
test.showMenu(null, function (popup) {
|
||||
test.checkMenu([item], [item], []);
|
||||
test.done();
|
||||
test.checkMenu([item], [], []);
|
||||
popup.hidePopup();
|
||||
|
||||
test.tabBrowser.removeTab(test.tabBrowser.selectedTab);
|
||||
test.tabBrowser.selectedTab = test.tab;
|
||||
|
||||
test.showMenu(null, function (popup) {
|
||||
test.checkMenu([item], [item], []);
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}, true);
|
||||
}, true);
|
||||
};
|
||||
this.tabBrowser.tabContainer.addEventListener("TabOpen", tablistener, false);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -349,8 +354,7 @@ exports.testSelectionContextButtonMatch = function (assert, done) {
|
|||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
window.getSelection().selectAllChildren(doc.body);
|
||||
let button = doc.getElementById("button");
|
||||
test.showMenu(button, function (popup) {
|
||||
test.showMenu("#button", function (popup) {
|
||||
test.checkMenu([item], [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -369,8 +373,7 @@ exports.testSelectionContextButtonNoMatch = function (assert, done) {
|
|||
});
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
let button = doc.getElementById("button");
|
||||
test.showMenu(button, function (popup) {
|
||||
test.showMenu("#button", function (popup) {
|
||||
test.checkMenu([item], [item], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -436,55 +439,6 @@ exports.testURLContextNoMatch = function (assert, done) {
|
|||
};
|
||||
|
||||
|
||||
// Removing a non-matching URL context after its item is created and the page is
|
||||
// loaded should cause the item's content script to be evaluated when the
|
||||
// context menu is next opened.
|
||||
exports.testURLContextRemove = function (assert, done) {
|
||||
let test = new TestHelper(assert, done);
|
||||
let loader = test.newLoader();
|
||||
|
||||
let shouldBeEvaled = false;
|
||||
let context = loader.cm.URLContext("*.bogus.com");
|
||||
let item = loader.cm.Item({
|
||||
label: "item",
|
||||
context: context,
|
||||
contentScript: 'self.postMessage("ok"); self.on("context", function () true);',
|
||||
onMessage: function (msg) {
|
||||
assert.ok(shouldBeEvaled,
|
||||
"content script should be evaluated when expected");
|
||||
assert.equal(msg, "ok", "Should have received the right message");
|
||||
shouldBeEvaled = false;
|
||||
}
|
||||
});
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(null, function (popup) {
|
||||
test.checkMenu([item], [item], []);
|
||||
|
||||
item.context.remove(context);
|
||||
|
||||
shouldBeEvaled = true;
|
||||
|
||||
test.hideMenu(function () {
|
||||
test.showMenu(null, function (popup) {
|
||||
test.checkMenu([item], [], []);
|
||||
|
||||
assert.ok(!shouldBeEvaled,
|
||||
"content script should have been evaluated");
|
||||
|
||||
test.hideMenu(function () {
|
||||
// Shouldn't get evaluated again
|
||||
test.showMenu(null, function (popup) {
|
||||
test.checkMenu([item], [], []);
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Loading a new page in the same tab should correctly start a new worker for
|
||||
// any content scripts
|
||||
exports.testPageReload = function (assert, done) {
|
||||
|
@ -772,7 +726,7 @@ exports.testContentContextMatchActiveElement = function (assert, done) {
|
|||
];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("image"), function (popup) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu(items, [items[2], items[3]], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -810,7 +764,7 @@ exports.testContentContextNoMatchActiveElement = function (assert, done) {
|
|||
];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("image"), function (popup) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu(items, items, []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -848,7 +802,7 @@ exports.testContentContextNoMatchActiveElement = function (assert, done) {
|
|||
];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("image"), function (popup) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu(items, items, []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -915,7 +869,6 @@ exports.testContentScriptFile = function (assert, done) {
|
|||
itemScript[1].resolve();
|
||||
}
|
||||
});
|
||||
console.log(item.contentScriptFile, item2.contentScriptFile);
|
||||
|
||||
test.showMenu(null, function (popup) {
|
||||
test.checkMenu([item, item2], [], []);
|
||||
|
@ -949,8 +902,7 @@ exports.testContentContextArgs = function (assert, done) {
|
|||
});
|
||||
};
|
||||
|
||||
// Multiple contexts imply intersection, not union, and content context
|
||||
// listeners should not be called if all declarative contexts are not current.
|
||||
// Multiple contexts imply intersection, not union.
|
||||
exports.testMultipleContexts = function (assert, done) {
|
||||
let test = new TestHelper(assert, done);
|
||||
let loader = test.newLoader();
|
||||
|
@ -958,14 +910,10 @@ exports.testMultipleContexts = function (assert, done) {
|
|||
let item = new loader.cm.Item({
|
||||
label: "item",
|
||||
context: [loader.cm.SelectorContext("a[href]"), loader.cm.PageContext()],
|
||||
contentScript: 'self.on("context", function () self.postMessage());',
|
||||
onMessage: function () {
|
||||
test.fail("Context listener should not be called");
|
||||
}
|
||||
});
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("span-link"), function (popup) {
|
||||
test.showMenu("#span-link", function (popup) {
|
||||
test.checkMenu([item], [item], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -984,7 +932,7 @@ exports.testRemoveContext = function (assert, done) {
|
|||
});
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("image"), function (popup) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
|
||||
// The item should be present at first.
|
||||
test.checkMenu([item], [], []);
|
||||
|
@ -992,7 +940,7 @@ exports.testRemoveContext = function (assert, done) {
|
|||
|
||||
// Remove the img context and check again.
|
||||
item.context.remove(ctxt);
|
||||
test.showMenu(doc.getElementById("image"), function (popup) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu([item], [item], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -1000,6 +948,87 @@ exports.testRemoveContext = function (assert, done) {
|
|||
});
|
||||
};
|
||||
|
||||
// Once a context is removed, it should no longer cause its item to appear.
|
||||
exports.testSetContextRemove = function (assert, done) {
|
||||
let test = new TestHelper(assert, done);
|
||||
let loader = test.newLoader();
|
||||
|
||||
let ctxt = loader.cm.SelectorContext("img");
|
||||
let item = new loader.cm.Item({
|
||||
label: "item",
|
||||
context: ctxt
|
||||
});
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
|
||||
// The item should be present at first.
|
||||
test.checkMenu([item], [], []);
|
||||
popup.hidePopup();
|
||||
|
||||
// Remove the img context and check again.
|
||||
item.context = [];
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu([item], [item], []);
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Once a context is added, it should affect whether the item appears.
|
||||
exports.testAddContext = function (assert, done) {
|
||||
let test = new TestHelper(assert, done);
|
||||
let loader = test.newLoader();
|
||||
|
||||
let ctxt = loader.cm.SelectorContext("img");
|
||||
let item = new loader.cm.Item({
|
||||
label: "item"
|
||||
});
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
|
||||
// The item should not be present at first.
|
||||
test.checkMenu([item], [item], []);
|
||||
popup.hidePopup();
|
||||
|
||||
// Add the img context and check again.
|
||||
item.context.add(ctxt);
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu([item], [], []);
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Once a context is added, it should affect whether the item appears.
|
||||
exports.testSetContextAdd = function (assert, done) {
|
||||
let test = new TestHelper(assert, done);
|
||||
let loader = test.newLoader();
|
||||
|
||||
let ctxt = loader.cm.SelectorContext("img");
|
||||
let item = new loader.cm.Item({
|
||||
label: "item"
|
||||
});
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
|
||||
// The item should not be present at first.
|
||||
test.checkMenu([item], [item], []);
|
||||
popup.hidePopup();
|
||||
|
||||
// Add the img context and check again.
|
||||
item.context = [ctxt];
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu([item], [], []);
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Lots of items should overflow into the overflow submenu.
|
||||
exports.testOverflow = function (assert, done) {
|
||||
|
@ -1636,12 +1665,12 @@ exports.testOverflowTransition = function (assert, done) {
|
|||
let allItems = pItems.concat(aItems);
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("link"), function (popup) {
|
||||
test.showMenu("#link", function (popup) {
|
||||
// The menu should contain all items and will overflow
|
||||
test.checkMenu(allItems, [], []);
|
||||
popup.hidePopup();
|
||||
|
||||
test.showMenu(doc.getElementById("text"), function (popup) {
|
||||
test.showMenu("#text", function (popup) {
|
||||
// Only contains hald the items and will not overflow
|
||||
test.checkMenu(allItems, aItems, []);
|
||||
popup.hidePopup();
|
||||
|
@ -1651,12 +1680,12 @@ exports.testOverflowTransition = function (assert, done) {
|
|||
test.checkMenu(allItems, allItems, []);
|
||||
popup.hidePopup();
|
||||
|
||||
test.showMenu(doc.getElementById("text"), function (popup) {
|
||||
test.showMenu("#text", function (popup) {
|
||||
// Only contains hald the items and will not overflow
|
||||
test.checkMenu(allItems, aItems, []);
|
||||
popup.hidePopup();
|
||||
|
||||
test.showMenu(doc.getElementById("link"), function (popup) {
|
||||
test.showMenu("#link", function (popup) {
|
||||
// The menu should contain all items and will overflow
|
||||
test.checkMenu(allItems, [], []);
|
||||
popup.hidePopup();
|
||||
|
@ -1666,7 +1695,7 @@ exports.testOverflowTransition = function (assert, done) {
|
|||
test.checkMenu(allItems, allItems, []);
|
||||
popup.hidePopup();
|
||||
|
||||
test.showMenu(doc.getElementById("link"), function (popup) {
|
||||
test.showMenu("#link", function (popup) {
|
||||
// The menu should contain all items and will overflow
|
||||
test.checkMenu(allItems, [], []);
|
||||
test.done();
|
||||
|
@ -1758,7 +1787,7 @@ exports.testMenuCommand = function (assert, done) {
|
|||
});
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("span-link"), function (popup) {
|
||||
test.showMenu("#span-link", function (popup) {
|
||||
test.checkMenu([topMenu], [], []);
|
||||
let topMenuElt = test.getItemElt(popup, topMenu);
|
||||
let topMenuPopup = topMenuElt.firstChild;
|
||||
|
@ -1884,7 +1913,7 @@ exports.testMenuClick = function (assert, done) {
|
|||
});
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("span-link"), function (popup) {
|
||||
test.showMenu("#span-link", function (popup) {
|
||||
test.checkMenu([topMenu], [], []);
|
||||
let topMenuElt = test.getItemElt(popup, topMenu);
|
||||
let topMenuPopup = topMenuElt.firstChild;
|
||||
|
@ -2224,7 +2253,7 @@ exports.testDrawImageOnClickNode = function (assert, done) {
|
|||
test.done();
|
||||
}
|
||||
});
|
||||
test.showMenu(doc.getElementById("image"), function (popup) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu([item], [], []);
|
||||
test.getItemElt(popup, item).click();
|
||||
});
|
||||
|
@ -2560,7 +2589,7 @@ exports.testAlreadyOpenIframe = function (assert, done) {
|
|||
let item = new loader.cm.Item({
|
||||
label: "item"
|
||||
});
|
||||
test.showMenu(doc.getElementById("iframe"), function (popup) {
|
||||
test.showMenu("#iframe", function (popup) {
|
||||
test.checkMenu([item], [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3004,7 +3033,7 @@ exports.testSubItemDefaultVisible = function (assert, done) {
|
|||
let hiddenItems = [items[0].items[2]];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("image"), function (popup) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu(items, hiddenItems, []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3175,7 +3204,7 @@ exports.testSelectionInInnerFrameMatch = function (assert, done) {
|
|||
let frame = doc.getElementById("iframe");
|
||||
frame.contentWindow.getSelection().selectAllChildren(frame.contentDocument.body);
|
||||
|
||||
test.showMenu(frame.contentDocument.getElementById("text"), function (popup) {
|
||||
test.showMenu(["#iframe", "#text"], function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3201,7 +3230,7 @@ exports.testSelectionInOuterFrameNoMatch = function (assert, done) {
|
|||
let frame = doc.getElementById("iframe");
|
||||
window.getSelection().selectAllChildren(doc.body);
|
||||
|
||||
test.showMenu(frame.contentDocument.getElementById("text"), function (popup) {
|
||||
test.showMenu(["#iframe", "#text"], function (popup) {
|
||||
test.checkMenu(items, items, []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3288,7 +3317,7 @@ exports.testPredicateContextTargetName = function (assert, done) {
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("button"), function (popup) {
|
||||
test.showMenu("#button", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3310,7 +3339,7 @@ exports.testPredicateContextTargetIDSet = function (assert, done) {
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("button"), function (popup) {
|
||||
test.showMenu("#button", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3331,7 +3360,7 @@ exports.testPredicateContextTargetIDNotSet = function (assert, done) {
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) {
|
||||
test.showMenu(".predicate-test-a", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3352,7 +3381,7 @@ exports.testPredicateContextTextBoxIsEditable = function (assert, done) {
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("textbox"), function (popup) {
|
||||
test.showMenu("#textbox", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3373,7 +3402,7 @@ exports.testPredicateContextReadonlyTextBoxIsNotEditable = function (assert, don
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("readonly-textbox"), function (popup) {
|
||||
test.showMenu("#readonly-textbox", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3394,7 +3423,7 @@ exports.testPredicateContextDisabledTextBoxIsNotEditable = function (assert, don
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("disabled-textbox"), function (popup) {
|
||||
test.showMenu("#disabled-textbox", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3415,7 +3444,7 @@ exports.testPredicateContextTextAreaIsEditable = function (assert, done) {
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("textfield"), function (popup) {
|
||||
test.showMenu("#textfield", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3436,7 +3465,7 @@ exports.testPredicateContextButtonIsNotEditable = function (assert, done) {
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("button"), function (popup) {
|
||||
test.showMenu("#button", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3458,7 +3487,7 @@ exports.testPredicateContextNonInputIsNotEditable = function (assert, done) {
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("image"), function (popup) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3480,7 +3509,7 @@ exports.testPredicateContextEditableElement = function (assert, done) {
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("editable"), function (popup) {
|
||||
test.showMenu("#editable", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3549,9 +3578,8 @@ exports.testPredicateContextSelectionInTextBox = function (assert, done) {
|
|||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
let textbox = doc.getElementById("textbox");
|
||||
textbox.focus();
|
||||
textbox.setSelectionRange(3, 6);
|
||||
test.showMenu(textbox, function (popup) {
|
||||
test.selectRange("#textbox", 3, 6);
|
||||
test.showMenu("#textbox", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3574,7 +3602,7 @@ exports.testPredicateContextTargetSrcSet = function (assert, done) {
|
|||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
image = doc.getElementById("image");
|
||||
test.showMenu(image, function (popup) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3595,7 +3623,7 @@ exports.testPredicateContextTargetSrcNotSet = function (assert, done) {
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("link"), function (popup) {
|
||||
test.showMenu("#link", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3618,7 +3646,7 @@ exports.testPredicateContextTargetLinkSet = function (assert, done) {
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) {
|
||||
test.showMenu(".predicate-test-a", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3639,7 +3667,7 @@ exports.testPredicateContextTargetLinkNotSet = function (assert, done) {
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("image"), function (popup) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3660,7 +3688,7 @@ exports.testPredicateContextTargetLinkSetNestedImage = function (assert, done) {
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("predicate-test-nested-image"), function (popup) {
|
||||
test.showMenu("#predicate-test-nested-image", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3681,7 +3709,7 @@ exports.testPredicateContextTargetLinkSetNestedStructure = function (assert, don
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("predicate-test-nested-structure"), function (popup) {
|
||||
test.showMenu("#predicate-test-nested-structure", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3703,7 +3731,7 @@ exports.testPredicateContextTargetValueSet = function (assert, done) {
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("textbox"), function (popup) {
|
||||
test.showMenu("#textbox", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -3724,7 +3752,7 @@ exports.testPredicateContextTargetValueNotSet = function (assert, done) {
|
|||
})];
|
||||
|
||||
test.withTestDoc(function (window, doc) {
|
||||
test.showMenu(doc.getElementById("image"), function (popup) {
|
||||
test.showMenu("#image", function (popup) {
|
||||
test.checkMenu(items, [], []);
|
||||
test.done();
|
||||
});
|
||||
|
@ -4098,14 +4126,69 @@ TestHelper.prototype = {
|
|||
OVERFLOW_THRESH_DEFAULT);
|
||||
},
|
||||
|
||||
// Opens the context menu on the current page. If targetNode is null, the
|
||||
// Loads scripts necessary in the content process
|
||||
loadFrameScript: function(browser = this.browserWindow.gBrowser.selectedBrowser) {
|
||||
function frame_script() {
|
||||
let { interfaces: Ci } = Components;
|
||||
addMessageListener('test:contextmenu', ({ data: { selectors } }) => {
|
||||
let targetNode = null;
|
||||
let contentWin = content;
|
||||
if (selectors) {
|
||||
while (selectors.length) {
|
||||
targetNode = contentWin.document.querySelector(selectors.shift());
|
||||
if (selectors.length)
|
||||
contentWin = targetNode.contentWindow;
|
||||
}
|
||||
}
|
||||
|
||||
let rect = targetNode ?
|
||||
targetNode.getBoundingClientRect() :
|
||||
{ left: 0, top: 0, width: 0, height: 0 };
|
||||
contentWin.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils)
|
||||
.sendMouseEvent('contextmenu',
|
||||
rect.left + (rect.width / 2),
|
||||
rect.top + (rect.height / 2),
|
||||
2, 1, 0);
|
||||
});
|
||||
|
||||
addMessageListener('test:ping', () => {
|
||||
sendAsyncMessage('test:pong');
|
||||
});
|
||||
|
||||
addMessageListener('test:select', ({ data: { selector, start, end } }) => {
|
||||
let element = content.document.querySelector(selector);
|
||||
element.focus();
|
||||
if (end === null)
|
||||
end = element.value.length;
|
||||
element.setSelectionRange(start, end);
|
||||
});
|
||||
}
|
||||
|
||||
let messageManager = browser.messageManager;
|
||||
messageManager.loadFrameScript("data:,(" + frame_script.toString() + ")();", true);
|
||||
},
|
||||
|
||||
selectRange: function(selector, start, end) {
|
||||
let messageManager = this.browserWindow.gBrowser.selectedBrowser.messageManager;
|
||||
messageManager.sendAsyncMessage('test:select', { selector, start, end });
|
||||
},
|
||||
|
||||
// Opens the context menu on the current page. If selectors is null, the
|
||||
// menu is opened in the top-left corner. onShowncallback is passed the
|
||||
// popup.
|
||||
showMenu: function(targetNode, onshownCallback) {
|
||||
// popup. selectors is an array of selectors. Starting from the main document
|
||||
// each selector points to an iframe, the last selector gives the target node.
|
||||
// In the simple case of a single selector just that string can be passed
|
||||
// instead of an array
|
||||
showMenu: function(selectors, onshownCallback) {
|
||||
let { promise, resolve } = defer();
|
||||
|
||||
function sendEvent() {
|
||||
this.delayedEventListener(this.browserWindow, "popupshowing",
|
||||
if (selectors && !Array.isArray(selectors))
|
||||
selectors = [selectors];
|
||||
|
||||
let sendEvent = () => {
|
||||
let menu = this.browserWindow.document.getElementById("contentAreaContextMenu");
|
||||
this.delayedEventListener(menu, "popupshowing",
|
||||
function (e) {
|
||||
let popup = e.target;
|
||||
if (onshownCallback) {
|
||||
|
@ -4114,35 +4197,41 @@ TestHelper.prototype = {
|
|||
resolve(popup);
|
||||
}, false);
|
||||
|
||||
let rect = targetNode ?
|
||||
targetNode.getBoundingClientRect() :
|
||||
{ left: 0, top: 0, width: 0, height: 0 };
|
||||
let contentWin = targetNode ? targetNode.ownerDocument.defaultView
|
||||
: this.browserWindow.content;
|
||||
contentWin.
|
||||
QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDOMWindowUtils).
|
||||
sendMouseEvent("contextmenu",
|
||||
rect.left + (rect.width / 2),
|
||||
rect.top + (rect.height / 2),
|
||||
2, 1, 0);
|
||||
let messageManager = this.browserWindow.gBrowser.selectedBrowser.messageManager;
|
||||
messageManager.sendAsyncMessage('test:contextmenu', { selectors });
|
||||
}
|
||||
|
||||
// Bounces an asynchronous message through the browser message manager.
|
||||
// This ensures that any pending messages have been delivered to the frame
|
||||
// scripts and so the remote proxies have been updated
|
||||
let flushMessages = () => {
|
||||
let listener = () => {
|
||||
messageManager.removeMessageListener('test:pong', listener);
|
||||
sendEvent();
|
||||
};
|
||||
|
||||
let messageManager = this.browserWindow.gBrowser.selectedBrowser.messageManager;
|
||||
messageManager.addMessageListener('test:pong', listener);
|
||||
messageManager.sendAsyncMessage('test:ping');
|
||||
}
|
||||
|
||||
// If a new tab or window has not yet been opened, open a new tab now. For
|
||||
// some reason using the tab already opened when the test starts causes
|
||||
// leaks. See bug 566351 for details.
|
||||
if (!targetNode && !this.oldSelectedTab && !this.oldBrowserWindow) {
|
||||
if (!selectors && !this.oldSelectedTab && !this.oldBrowserWindow) {
|
||||
this.oldSelectedTab = this.tabBrowser.selectedTab;
|
||||
this.tab = this.tabBrowser.addTab("about:blank");
|
||||
let browser = this.tabBrowser.getBrowserForTab(this.tab);
|
||||
|
||||
this.delayedEventListener(browser, "load", function () {
|
||||
this.tabBrowser.selectedTab = this.tab;
|
||||
sendEvent.call(this);
|
||||
this.loadFrameScript();
|
||||
flushMessages();
|
||||
}, true);
|
||||
}
|
||||
else
|
||||
sendEvent.call(this);
|
||||
else {
|
||||
flushMessages();
|
||||
}
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
@ -4155,9 +4244,14 @@ TestHelper.prototype = {
|
|||
|
||||
// Opens a new browser window. The window will be closed automatically when
|
||||
// done() is called.
|
||||
withNewWindow: function (onloadCallback) {
|
||||
let win = this.browserWindow.OpenBrowserWindow();
|
||||
this.delayedEventListener(win, "load", onloadCallback, true);
|
||||
withNewWindow: function (onloadCallback, makePrivate = false) {
|
||||
let win = this.browserWindow.OpenBrowserWindow({ private: makePrivate });
|
||||
observers.once("browser-delayed-startup-finished", () => {
|
||||
// Open a new tab so we can make sure it is remote and loaded
|
||||
win.gBrowser.selectedTab = win.gBrowser.addTab();
|
||||
this.loadFrameScript();
|
||||
this.delayedEventListener(win.gBrowser.selectedBrowser, "load", onloadCallback, true);
|
||||
});
|
||||
this.oldBrowserWindow = this.browserWindow;
|
||||
this.browserWindow = win;
|
||||
},
|
||||
|
@ -4165,10 +4259,7 @@ TestHelper.prototype = {
|
|||
// Opens a new private browser window. The window will be closed
|
||||
// automatically when done() is called.
|
||||
withNewPrivateWindow: function (onloadCallback) {
|
||||
let win = this.browserWindow.OpenBrowserWindow({private: true});
|
||||
this.delayedEventListener(win, "load", onloadCallback, true);
|
||||
this.oldBrowserWindow = this.browserWindow;
|
||||
this.browserWindow = win;
|
||||
this.withNewWindow(onloadCallback, true);
|
||||
},
|
||||
|
||||
// Opens a new tab with our test page in the current window. The tab will
|
||||
|
@ -4180,6 +4271,7 @@ TestHelper.prototype = {
|
|||
|
||||
this.delayedEventListener(browser, "load", function () {
|
||||
this.tabBrowser.selectedTab = this.tab;
|
||||
this.loadFrameScript();
|
||||
onloadCallback.call(this, browser.contentWindow, browser.contentDocument);
|
||||
}, true, function(evt) {
|
||||
return evt.target.location == TEST_DOC_URL;
|
||||
|
|
|
@ -8,22 +8,27 @@
|
|||
|
||||
this.EXPORTED_SYMBOLS = ['LogCapture'];
|
||||
|
||||
/**
|
||||
* readLogFile
|
||||
* Read in /dev/log/{{log}} in nonblocking mode, which will return -1 if
|
||||
* reading would block the thread.
|
||||
*
|
||||
* @param log {String} The log from which to read. Must be present in /dev/log
|
||||
* @return {Uint8Array} Raw log data
|
||||
*/
|
||||
let readLogFile = function(logLocation) {
|
||||
if (!this.ctypes) {
|
||||
const SYSTEM_PROPERTY_KEY_MAX = 32;
|
||||
const SYSTEM_PROPERTY_VALUE_MAX = 92;
|
||||
|
||||
function debug(msg) {
|
||||
dump('LogCapture.jsm: ' + msg + '\n');
|
||||
}
|
||||
|
||||
let LogCapture = {
|
||||
ensureLoaded: function() {
|
||||
if (!this.ctypes) {
|
||||
this.load();
|
||||
}
|
||||
},
|
||||
|
||||
load: function() {
|
||||
// load in everything on first use
|
||||
Components.utils.import('resource://gre/modules/ctypes.jsm', this);
|
||||
|
||||
this.lib = this.ctypes.open(this.ctypes.libraryName('c'));
|
||||
this.libc = this.ctypes.open(this.ctypes.libraryName('c'));
|
||||
|
||||
this.read = this.lib.declare('read',
|
||||
this.read = this.libc.declare('read',
|
||||
this.ctypes.default_abi,
|
||||
this.ctypes.int, // bytes read (out)
|
||||
this.ctypes.int, // file descriptor (in)
|
||||
|
@ -31,61 +36,124 @@ let readLogFile = function(logLocation) {
|
|||
this.ctypes.size_t // size_t size of buffer (in)
|
||||
);
|
||||
|
||||
this.open = this.lib.declare('open',
|
||||
this.open = this.libc.declare('open',
|
||||
this.ctypes.default_abi,
|
||||
this.ctypes.int, // file descriptor (returned)
|
||||
this.ctypes.char.ptr, // path
|
||||
this.ctypes.int // flags
|
||||
);
|
||||
|
||||
this.close = this.lib.declare('close',
|
||||
this.close = this.libc.declare('close',
|
||||
this.ctypes.default_abi,
|
||||
this.ctypes.int, // error code (returned)
|
||||
this.ctypes.int // file descriptor
|
||||
);
|
||||
}
|
||||
|
||||
const O_READONLY = 0;
|
||||
const O_NONBLOCK = 1 << 11;
|
||||
this.property_find_nth =
|
||||
this.libc.declare("__system_property_find_nth",
|
||||
this.ctypes.default_abi,
|
||||
this.ctypes.voidptr_t, // return value: nullable prop_info*
|
||||
this.ctypes.unsigned_int); // n: the index of the property to return
|
||||
|
||||
const BUF_SIZE = 2048;
|
||||
this.property_read =
|
||||
this.libc.declare("__system_property_read",
|
||||
this.ctypes.default_abi,
|
||||
this.ctypes.void_t, // return: none
|
||||
this.ctypes.voidptr_t, // non-null prop_info*
|
||||
this.ctypes.char.ptr, // key
|
||||
this.ctypes.char.ptr); // value
|
||||
|
||||
let BufType = this.ctypes.ArrayType(this.ctypes.char);
|
||||
let buf = new BufType(BUF_SIZE);
|
||||
let logArray = [];
|
||||
this.key_buf = this.ctypes.char.array(SYSTEM_PROPERTY_KEY_MAX)();
|
||||
this.value_buf = this.ctypes.char.array(SYSTEM_PROPERTY_VALUE_MAX)();
|
||||
},
|
||||
|
||||
let logFd = this.open(logLocation, O_READONLY | O_NONBLOCK);
|
||||
if (logFd === -1) {
|
||||
return null;
|
||||
}
|
||||
cleanup: function() {
|
||||
this.libc.close();
|
||||
|
||||
let readStart = Date.now();
|
||||
let readCount = 0;
|
||||
while (true) {
|
||||
let count = this.read(logFd, buf, BUF_SIZE);
|
||||
readCount += 1;
|
||||
this.read = null;
|
||||
this.open = null;
|
||||
this.close = null;
|
||||
this.property_find_nth = null;
|
||||
this.property_read = null;
|
||||
this.key_buf = null;
|
||||
this.value_buf = null;
|
||||
|
||||
if (count <= 0) {
|
||||
// log has return due to being nonblocking or running out of things
|
||||
break;
|
||||
this.libc = null;
|
||||
this.ctypes = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* readLogFile
|
||||
* Read in /dev/log/{{log}} in nonblocking mode, which will return -1 if
|
||||
* reading would block the thread.
|
||||
*
|
||||
* @param log {String} The log from which to read. Must be present in /dev/log
|
||||
* @return {Uint8Array} Raw log data
|
||||
*/
|
||||
readLogFile: function(logLocation) {
|
||||
this.ensureLoaded();
|
||||
|
||||
const O_READONLY = 0;
|
||||
const O_NONBLOCK = 1 << 11;
|
||||
|
||||
const BUF_SIZE = 2048;
|
||||
|
||||
let BufType = this.ctypes.ArrayType(this.ctypes.char);
|
||||
let buf = new BufType(BUF_SIZE);
|
||||
let logArray = [];
|
||||
|
||||
let logFd = this.open(logLocation, O_READONLY | O_NONBLOCK);
|
||||
if (logFd === -1) {
|
||||
return null;
|
||||
}
|
||||
for(let i = 0; i < count; i++) {
|
||||
logArray.push(buf[i]);
|
||||
|
||||
let readStart = Date.now();
|
||||
let readCount = 0;
|
||||
while (true) {
|
||||
let count = this.read(logFd, buf, BUF_SIZE);
|
||||
readCount += 1;
|
||||
|
||||
if (count <= 0) {
|
||||
// log has return due to being nonblocking or running out of things
|
||||
break;
|
||||
}
|
||||
for(let i = 0; i < count; i++) {
|
||||
logArray.push(buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
let logTypedArray = new Uint8Array(logArray);
|
||||
|
||||
this.close(logFd);
|
||||
|
||||
return logTypedArray;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all system properties as a dict with keys mapping to values
|
||||
*/
|
||||
readProperties: function() {
|
||||
this.ensureLoaded();
|
||||
let n = 0;
|
||||
let propertyDict = {};
|
||||
|
||||
while(true) {
|
||||
let prop_info = this.property_find_nth(n);
|
||||
if(prop_info.isNull()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// read the prop_info into the key and value buffers
|
||||
this.property_read(prop_info, this.key_buf, this.value_buf);
|
||||
let key = this.key_buf.readString();;
|
||||
let value = this.value_buf.readString()
|
||||
|
||||
propertyDict[key] = value;
|
||||
n++;
|
||||
}
|
||||
|
||||
return propertyDict;
|
||||
}
|
||||
|
||||
let logTypedArray = new Uint8Array(logArray);
|
||||
|
||||
this.close(logFd);
|
||||
|
||||
return logTypedArray;
|
||||
};
|
||||
|
||||
let cleanup = function() {
|
||||
this.lib.close();
|
||||
this.read = this.open = this.close = null;
|
||||
this.lib = null;
|
||||
this.ctypes = null;
|
||||
};
|
||||
|
||||
this.LogCapture = { readLogFile: readLogFile, cleanup: cleanup };
|
||||
this.LogCapture = LogCapture;
|
||||
|
|
|
@ -215,71 +215,14 @@ function prettyPrintLogArray(array) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parse an array of bytes as a properties file. The structure of the
|
||||
* properties file is derived from bionic/libc/bionic/system_properties.c
|
||||
* @param array {Uint8Array} Array containing property data
|
||||
* @return {Object} Map from property name to property value, both strings
|
||||
*/
|
||||
function parsePropertiesArray(array) {
|
||||
let data = new DataView(array.buffer);
|
||||
let byteString = String.fromCharCode.apply(null, array);
|
||||
|
||||
let properties = {};
|
||||
|
||||
let propIndex = 0;
|
||||
let propCount = data.getUint32(0, true);
|
||||
|
||||
// first TOC entry is at 32
|
||||
let tocOffset = 32;
|
||||
|
||||
const PROP_NAME_MAX = 32;
|
||||
const PROP_VALUE_MAX = 92;
|
||||
|
||||
while (propIndex < propCount) {
|
||||
// Retrieve offset from file start
|
||||
let infoOffset = data.getUint32(tocOffset, true) & 0xffffff;
|
||||
|
||||
// Now read the name, integer serial, and value
|
||||
let propName = "";
|
||||
let nameOffset = infoOffset;
|
||||
while (byteString[nameOffset] != "\0" &&
|
||||
(nameOffset - infoOffset) < PROP_NAME_MAX) {
|
||||
propName += byteString[nameOffset];
|
||||
nameOffset ++;
|
||||
}
|
||||
|
||||
infoOffset += PROP_NAME_MAX;
|
||||
// Skip serial number
|
||||
infoOffset += 4;
|
||||
|
||||
let propValue = "";
|
||||
nameOffset = infoOffset;
|
||||
while (byteString[nameOffset] != "\0" &&
|
||||
(nameOffset - infoOffset) < PROP_VALUE_MAX) {
|
||||
propValue += byteString[nameOffset];
|
||||
nameOffset ++;
|
||||
}
|
||||
|
||||
// Move to next table of contents entry
|
||||
tocOffset += 4;
|
||||
|
||||
properties[propName] = propValue;
|
||||
propIndex += 1;
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty-print an array read from the /dev/__properties__ file.
|
||||
* @param array {Uint8Array} File data array
|
||||
* Pretty-print an array read from the list of propreties.
|
||||
* @param {Object} Object representing the properties
|
||||
* @return {String} Human-readable string of property name: property value
|
||||
*/
|
||||
function prettyPrintPropertiesArray(array) {
|
||||
let properties = parsePropertiesArray(array);
|
||||
function prettyPrintPropertiesArray(properties) {
|
||||
let propertiesString = "";
|
||||
for(let propName in properties) {
|
||||
propertiesString += propName + ": " + properties[propName] + "\n";
|
||||
propertiesString += "[" + propName + "]: [" + properties[propName] + "]\n";
|
||||
}
|
||||
return propertiesString;
|
||||
}
|
||||
|
@ -294,7 +237,6 @@ function prettyPrintArray(array) {
|
|||
|
||||
this.LogParser = {
|
||||
parseLogArray: parseLogArray,
|
||||
parsePropertiesArray: parsePropertiesArray,
|
||||
prettyPrintArray: prettyPrintArray,
|
||||
prettyPrintLogArray: prettyPrintLogArray,
|
||||
prettyPrintPropertiesArray: prettyPrintPropertiesArray
|
||||
|
|
|
@ -79,7 +79,6 @@ let LogShake = {
|
|||
* Map of files which have log-type information to their parsers
|
||||
*/
|
||||
LOGS_WITH_PARSERS: {
|
||||
'/dev/__properties__': LogParser.prettyPrintPropertiesArray,
|
||||
'/dev/log/main': LogParser.prettyPrintLogArray,
|
||||
'/dev/log/system': LogParser.prettyPrintLogArray,
|
||||
'/dev/log/radio': LogParser.prettyPrintLogArray,
|
||||
|
@ -210,6 +209,14 @@ let LogShake = {
|
|||
*/
|
||||
readLogs: function() {
|
||||
let logArrays = {};
|
||||
|
||||
try {
|
||||
logArrays["properties"] =
|
||||
LogParser.prettyPrintPropertiesArray(LogCapture.readProperties());
|
||||
} catch (ex) {
|
||||
Cu.reportError("Unable to get device properties: " + ex);
|
||||
}
|
||||
|
||||
for (let loc in this.LOGS_WITH_PARSERS) {
|
||||
let logArray;
|
||||
try {
|
||||
|
|
Двоичные данные
b2g/components/test/unit/data/test_properties
Двоичные данные
b2g/components/test/unit/data/test_properties
Двоичный файл не отображается.
|
@ -8,7 +8,7 @@
|
|||
* log devices
|
||||
*/
|
||||
function run_test() {
|
||||
Components.utils.import('resource:///modules/LogCapture.jsm');
|
||||
Components.utils.import("resource:///modules/LogCapture.jsm");
|
||||
|
||||
function verifyLog(log) {
|
||||
// log exists
|
||||
|
@ -17,9 +17,14 @@ function run_test() {
|
|||
ok(log.length >= 0);
|
||||
}
|
||||
|
||||
let mainLog = LogCapture.readLogFile('/dev/log/main');
|
||||
let propertiesLog = LogCapture.readProperties();
|
||||
notEqual(propertiesLog, null, "Properties should not be null");
|
||||
notEqual(propertiesLog, undefined, "Properties should not be undefined");
|
||||
equal(propertiesLog["ro.kernel.qemu"], "1", "QEMU property should be 1");
|
||||
|
||||
let mainLog = LogCapture.readLogFile("/dev/log/main");
|
||||
verifyLog(mainLog);
|
||||
|
||||
let meminfoLog = LogCapture.readLogFile('/proc/meminfo');
|
||||
let meminfoLog = LogCapture.readLogFile("/proc/meminfo");
|
||||
verifyLog(meminfoLog);
|
||||
}
|
||||
|
|
|
@ -2,48 +2,73 @@
|
|||
|
||||
const {utils: Cu, classes: Cc, interfaces: Ci} = Components;
|
||||
|
||||
function debug(msg) {
|
||||
var timestamp = Date.now();
|
||||
dump("LogParser: " + timestamp + ": " + msg + "\n");
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
Cu.import('resource:///modules/LogParser.jsm');
|
||||
|
||||
let propertiesFile = do_get_file('data/test_properties');
|
||||
let loggerFile = do_get_file('data/test_logger_file');
|
||||
|
||||
let propertiesStream = makeStream(propertiesFile);
|
||||
let loggerStream = makeStream(loggerFile);
|
||||
|
||||
// Initialize arrays to hold the file contents (lengths are hardcoded)
|
||||
let propertiesArray = new Uint8Array(propertiesStream.readByteArray(65536));
|
||||
let loggerArray = new Uint8Array(loggerStream.readByteArray(4037));
|
||||
|
||||
propertiesStream.close();
|
||||
loggerStream.close();
|
||||
|
||||
let properties = LogParser.parsePropertiesArray(propertiesArray);
|
||||
let logMessages = LogParser.parseLogArray(loggerArray);
|
||||
|
||||
// Test arbitrary property entries for correctness
|
||||
equal(properties['ro.boot.console'], 'ttyHSL0');
|
||||
equal(properties['net.tcp.buffersize.lte'],
|
||||
'524288,1048576,2097152,262144,524288,1048576');
|
||||
|
||||
ok(logMessages.length === 58, 'There should be 58 messages in the log');
|
||||
|
||||
let expectedLogEntry = {
|
||||
processId: 271, threadId: 271,
|
||||
seconds: 790796, nanoseconds: 620000001, time: 790796620.000001,
|
||||
priority: 4, tag: 'Vold',
|
||||
message: 'Vold 2.1 (the revenge) firing up\n'
|
||||
};
|
||||
|
||||
deepEqual(expectedLogEntry, logMessages[0]);
|
||||
Cu.import("resource:///modules/LogParser.jsm");
|
||||
debug("Starting");
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
function makeStream(file) {
|
||||
var fileStream = Cc['@mozilla.org/network/file-input-stream;1']
|
||||
var fileStream = Cc["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Ci.nsIFileInputStream);
|
||||
fileStream.init(file, -1, -1, 0);
|
||||
var bis = Cc['@mozilla.org/binaryinputstream;1']
|
||||
var bis = Cc["@mozilla.org/binaryinputstream;1"]
|
||||
.createInstance(Ci.nsIBinaryInputStream);
|
||||
bis.setInputStream(fileStream);
|
||||
return bis;
|
||||
}
|
||||
|
||||
add_test(function test_parse_logfile() {
|
||||
let loggerFile = do_get_file("data/test_logger_file");
|
||||
|
||||
let loggerStream = makeStream(loggerFile);
|
||||
|
||||
// Initialize arrays to hold the file contents (lengths are hardcoded)
|
||||
let loggerArray = new Uint8Array(loggerStream.readByteArray(4037));
|
||||
|
||||
loggerStream.close();
|
||||
|
||||
let logMessages = LogParser.parseLogArray(loggerArray);
|
||||
|
||||
ok(logMessages.length === 58, "There should be 58 messages in the log");
|
||||
|
||||
let expectedLogEntry = {
|
||||
processId: 271, threadId: 271,
|
||||
seconds: 790796, nanoseconds: 620000001, time: 790796620.000001,
|
||||
priority: 4, tag: "Vold",
|
||||
message: "Vold 2.1 (the revenge) firing up\n"
|
||||
};
|
||||
|
||||
deepEqual(expectedLogEntry, logMessages[0]);
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_print_properties() {
|
||||
let properties = {
|
||||
"ro.secure": "1",
|
||||
"sys.usb.state": "diag,serial_smd,serial_tty,rmnet_bam,mass_storage,adb"
|
||||
};
|
||||
|
||||
let logMessages = LogParser.prettyPrintPropertiesArray(properties);
|
||||
let logMessagesArray = logMessages.split("\n");
|
||||
|
||||
ok(logMessagesArray.length === 3, "There should be 3 lines in the log.");
|
||||
notEqual(logMessagesArray[0], "", "First line should not be empty");
|
||||
notEqual(logMessagesArray[1], "", "Second line should not be empty");
|
||||
equal(logMessagesArray[2], "", "Last line should be empty");
|
||||
|
||||
let expectedLog = [
|
||||
"[ro.secure]: [1]",
|
||||
"[sys.usb.state]: [diag,serial_smd,serial_tty,rmnet_bam,mass_storage,adb]",
|
||||
""
|
||||
].join("\n");
|
||||
|
||||
deepEqual(expectedLog, logMessages);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
|
|
@ -8,19 +8,19 @@
|
|||
|
||||
/* disable use strict warning */
|
||||
/* jshint -W097 */
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import('resource://gre/modules/LogCapture.jsm');
|
||||
Cu.import('resource://gre/modules/LogShake.jsm');
|
||||
Cu.import("resource://gre/modules/LogCapture.jsm");
|
||||
Cu.import("resource://gre/modules/LogShake.jsm");
|
||||
|
||||
// Force logshake to handle a device motion event with given components
|
||||
// Does not use SystemAppProxy because event needs special
|
||||
// accelerationIncludingGravity property
|
||||
function sendDeviceMotionEvent(x, y, z) {
|
||||
let event = {
|
||||
type: 'devicemotion',
|
||||
type: "devicemotion",
|
||||
accelerationIncludingGravity: {
|
||||
x: x,
|
||||
y: y,
|
||||
|
@ -34,7 +34,7 @@ function sendDeviceMotionEvent(x, y, z) {
|
|||
// conditions.
|
||||
function sendScreenChangeEvent(screenEnabled) {
|
||||
let event = {
|
||||
type: 'screenchange',
|
||||
type: "screenchange",
|
||||
detail: {
|
||||
screenEnabled: screenEnabled
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ function sendScreenChangeEvent(screenEnabled) {
|
|||
|
||||
function debug(msg) {
|
||||
var timestamp = Date.now();
|
||||
dump('LogShake: ' + timestamp + ': ' + msg);
|
||||
dump("LogShake: " + timestamp + ": " + msg);
|
||||
}
|
||||
|
||||
add_test(function test_do_log_capture_after_shaking() {
|
||||
|
@ -61,7 +61,7 @@ add_test(function test_do_log_capture_after_shaking() {
|
|||
sendDeviceMotionEvent(9001, 9001, 9001);
|
||||
|
||||
ok(readLocations.length > 0,
|
||||
'LogShake should attempt to read at least one log');
|
||||
"LogShake should attempt to read at least one log");
|
||||
|
||||
LogShake.uninit();
|
||||
run_next_test();
|
||||
|
@ -81,15 +81,15 @@ add_test(function test_do_nothing_when_resting() {
|
|||
sendDeviceMotionEvent(0, 9.8, 9.8);
|
||||
|
||||
ok(readLocations.length === 0,
|
||||
'LogShake should not read any logs');
|
||||
"LogShake should not read any logs");
|
||||
|
||||
debug('test_do_nothing_when_resting: stop');
|
||||
debug("test_do_nothing_when_resting: stop");
|
||||
LogShake.uninit();
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_do_nothing_when_disabled() {
|
||||
debug('test_do_nothing_when_disabled: start');
|
||||
debug("test_do_nothing_when_disabled: start");
|
||||
// Disable LogShake
|
||||
LogShake.uninit();
|
||||
|
||||
|
@ -103,7 +103,7 @@ add_test(function test_do_nothing_when_disabled() {
|
|||
sendDeviceMotionEvent(0, 9001, 9001);
|
||||
|
||||
ok(readLocations.length === 0,
|
||||
'LogShake should not read any logs');
|
||||
"LogShake should not read any logs");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
@ -126,7 +126,7 @@ add_test(function test_do_nothing_when_screen_off() {
|
|||
sendDeviceMotionEvent(0, 9001, 9001);
|
||||
|
||||
ok(readLocations.length === 0,
|
||||
'LogShake should not read any logs');
|
||||
"LogShake should not read any logs");
|
||||
|
||||
// Restore the screen
|
||||
sendScreenChangeEvent(true);
|
||||
|
@ -149,7 +149,7 @@ add_test(function test_do_log_capture_resilient_readLogFile() {
|
|||
sendDeviceMotionEvent(9001, 9001, 9001);
|
||||
|
||||
ok(readLocations.length > 0,
|
||||
'LogShake should attempt to read at least one log');
|
||||
"LogShake should attempt to read at least one log");
|
||||
|
||||
LogShake.uninit();
|
||||
run_next_test();
|
||||
|
@ -172,13 +172,13 @@ add_test(function test_do_log_capture_resilient_parseLog() {
|
|||
sendDeviceMotionEvent(9001, 9001, 9001);
|
||||
|
||||
ok(readLocations.length > 0,
|
||||
'LogShake should attempt to read at least one log');
|
||||
"LogShake should attempt to read at least one log");
|
||||
|
||||
LogShake.uninit();
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
debug('Starting');
|
||||
debug("Starting");
|
||||
run_next_test();
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ tail =
|
|||
|
||||
support-files =
|
||||
data/test_logger_file
|
||||
data/test_properties
|
||||
|
||||
[test_bug793310.js]
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae3a84acaab80a5b35d5542d63e68462273c8a1b"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4aee256937afe9db2520752650685ba61ce6097d"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ae3a84acaab80a5b35d5542d63e68462273c8a1b"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="4aee256937afe9db2520752650685ba61ce6097d"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="67f2907bc340bad250b4ea6ce2902b52896c9ef0"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="a47dd04f8f66e42fd331711140f2c3e2fed0767d"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="11ad0ea69796915552c9bae148d81fddf9856ddb"/>
|
||||
<!-- Stock Android things -->
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae3a84acaab80a5b35d5542d63e68462273c8a1b"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4aee256937afe9db2520752650685ba61ce6097d"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="11ad0ea69796915552c9bae148d81fddf9856ddb"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae3a84acaab80a5b35d5542d63e68462273c8a1b"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4aee256937afe9db2520752650685ba61ce6097d"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ae3a84acaab80a5b35d5542d63e68462273c8a1b"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="4aee256937afe9db2520752650685ba61ce6097d"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="67f2907bc340bad250b4ea6ce2902b52896c9ef0"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="a47dd04f8f66e42fd331711140f2c3e2fed0767d"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="11ad0ea69796915552c9bae148d81fddf9856ddb"/>
|
||||
<!-- Stock Android things -->
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae3a84acaab80a5b35d5542d63e68462273c8a1b"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4aee256937afe9db2520752650685ba61ce6097d"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</project>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae3a84acaab80a5b35d5542d63e68462273c8a1b"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4aee256937afe9db2520752650685ba61ce6097d"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="11ad0ea69796915552c9bae148d81fddf9856ddb"/>
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
"remote": "",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "587d98bf26625137015c17d5b937bf06bd055dd0",
|
||||
"revision": "67a3857cc29aaa2b2f890b4ed575065f6e5a7691",
|
||||
"repo_path": "integration/gaia-central"
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ae3a84acaab80a5b35d5542d63e68462273c8a1b"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="4aee256937afe9db2520752650685ba61ce6097d"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ae3a84acaab80a5b35d5542d63e68462273c8a1b"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="4aee256937afe9db2520752650685ba61ce6097d"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="ae3a84acaab80a5b35d5542d63e68462273c8a1b"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4aee256937afe9db2520752650685ba61ce6097d"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="11ad0ea69796915552c9bae148d81fddf9856ddb"/>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="ae3a84acaab80a5b35d5542d63e68462273c8a1b"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="4aee256937afe9db2520752650685ba61ce6097d"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
|
|
@ -132,7 +132,6 @@
|
|||
@BINPATH@/components/autocomplete.xpt
|
||||
@BINPATH@/components/autoconfig.xpt
|
||||
@BINPATH@/components/browsercompsbase.xpt
|
||||
@BINPATH@/components/browser-element.xpt
|
||||
@BINPATH@/components/browser-feeds.xpt
|
||||
@BINPATH@/components/caps.xpt
|
||||
@BINPATH@/components/chardet.xpt
|
||||
|
|
|
@ -5369,7 +5369,10 @@
|
|||
}, () => {
|
||||
// If the promise rejected, that means we don't want to actually
|
||||
// flip the deck, so we cancel the tab switch.
|
||||
gBrowser._cancelTabSwitch(toTab);
|
||||
// We need to nullcheck the method we're about to call because
|
||||
// the binding might be dead at this point.
|
||||
if (gBrowser._cancelTabSwitch)
|
||||
gBrowser._cancelTabSwitch(toTab);
|
||||
});
|
||||
|
||||
return val;
|
||||
|
|
|
@ -124,7 +124,7 @@ skip-if = os == "linux" || e10s # Linux: Intermittent failures, bug 951680; e10s
|
|||
[browser_autocomplete_a11y_label.js]
|
||||
skip-if = e10s # Bug ????? - no e10s switch-to-tab support yet
|
||||
[browser_backButtonFitts.js]
|
||||
skip-if = os != "win" || e10s # The Fitts Law back button is only supported on Windows (bug 571454) / e10s - Bug ?????? test touches content (attempts to add an event listener directly to the contentWindow)
|
||||
skip-if = os != "win" || e10s # The Fitts Law back button is only supported on Windows (bug 571454) / e10s - Bug 1099154: test touches content (attempts to add an event listener directly to the contentWindow)
|
||||
[browser_blob-channelname.js]
|
||||
[browser_bookmark_titles.js]
|
||||
skip-if = buildapp == 'mulet' || toolkit == "windows" || e10s # Disabled on Windows due to frequent failures (bugs 825739, 841341) / e10s - Bug 1094205 - places doesn't return the right thing in e10s mode, for some reason
|
||||
|
@ -165,7 +165,7 @@ skip-if = e10s # Bug ?????? - test directly manipulates content (eg, var expertD
|
|||
[browser_bug432599.js]
|
||||
[browser_bug435035.js]
|
||||
[browser_bug435325.js]
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug 1099156 - test directly manipulates content
|
||||
[browser_bug441778.js]
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
|
||||
[browser_bug455852.js]
|
||||
|
@ -265,7 +265,7 @@ skip-if = buildapp == 'mulet' || os == "mac" # mac: Intermittent failures, bug 9
|
|||
[browser_bug678392.js]
|
||||
skip-if = e10s # Bug ?????? - Obscure non-windows failures ("Snapshot array has correct length of 1 after loading one page. - Got 0, expected 1" and more)
|
||||
[browser_bug710878.js]
|
||||
skip-if = e10s # Bug ?????? - test directly manipulates content (doc.querySelector)
|
||||
skip-if = e10s # Bug 1100653 - test uses waitForFocus on content
|
||||
[browser_bug719271.js]
|
||||
skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
|
||||
[browser_bug724239.js]
|
||||
|
@ -352,10 +352,10 @@ skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (
|
|||
[browser_middleMouse_noJSPaste.js]
|
||||
skip-if = e10s # Bug 921952 - Content:Click event issues
|
||||
[browser_minimize.js]
|
||||
skip-if = e10s # Bug ?????? - test directly manipulates content (TypeError: gBrowser.docShell is null)
|
||||
skip-if = e10s # Bug 1100664 - test directly access content docShells (TypeError: gBrowser.docShell is null)
|
||||
[browser_mixedcontent_securityflags.js]
|
||||
[browser_notification_tab_switching.js]
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug ?????? - uncaught exception - Error: cannot ipc non-cpow object at chrome://mochitests/content/browser/browser/base/content/test/general/browser_notification_tab_switching.js:32
|
||||
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)
|
||||
[browser_offlineQuotaNotification.js]
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug 1093603 - test breaks with PopupNotifications.panel.firstElementChild is null
|
||||
[browser_overflowScroll.js]
|
||||
|
@ -373,7 +373,7 @@ skip-if = asan # Disabled because it takes a long time (see test for more inform
|
|||
[browser_plainTextLinks.js]
|
||||
skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
|
||||
[browser_popupUI.js]
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (tries to get a popup element directly from content)
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug 1100707 - test fails in e10s because it can't get accel-w to close the popup (?)
|
||||
[browser_popup_blocker.js]
|
||||
[browser_printpreview.js]
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug ?????? - timeout after logging "Error: Channel closing: too late to send/recv, messages will be lost"
|
||||
|
@ -402,11 +402,11 @@ skip-if = true # disabled until the tree view is added
|
|||
# back to the clear recent history dialog (sanitize.xul), if
|
||||
# it ever is (bug 480169)
|
||||
[browser_save_link-perwindowpb.js]
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (event.target)
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
|
||||
[browser_save_private_link_perwindowpb.js]
|
||||
skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
|
||||
[browser_save_video.js]
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (event.target)
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug 1100698 - test uses synthesizeMouse and then does a load of other stuff that breaks in e10s
|
||||
[browser_save_video_frame.js]
|
||||
[browser_scope.js]
|
||||
[browser_searchSuggestionUI.js]
|
||||
|
@ -439,7 +439,7 @@ skip-if = e10s
|
|||
[browser_tabopen_reflows.js]
|
||||
skip-if = e10s # Bug ?????? - test needs to be updated for e10s (captures a stack that isn't correct in e10s)
|
||||
[browser_tabs_isActive.js]
|
||||
skip-if = e10s # Bug ?????? - test directly manipulates content (tries to get/set attributes directly on content docshell)
|
||||
skip-if = e10s # Bug 1100664 - test relies on linkedBrowser.docShell
|
||||
[browser_tabs_owner.js]
|
||||
[browser_trackingUI.js]
|
||||
skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
|
||||
|
@ -449,7 +449,7 @@ support-files =
|
|||
[browser_typeAheadFind.js]
|
||||
skip-if = buildapp == 'mulet' || e10s # Bug 921935 - focusmanager issues with e10s (test calls waitForFocus)
|
||||
[browser_unloaddialogs.js]
|
||||
skip-if = e10s # Bug ?????? - test uses chrome windowMediator to try and see alert() from content
|
||||
skip-if = e10s # Bug 1100700 - test relies on unload event firing on closed tabs, which it doesn't
|
||||
[browser_urlHighlight.js]
|
||||
[browser_urlbarAutoFillTrimURLs.js]
|
||||
skip-if = e10s # Bug 1093941 - Waits indefinitely for onSearchComplete
|
||||
|
@ -459,7 +459,6 @@ skip-if = e10s # Bug 1093941 - used to cause obscure non-windows child process c
|
|||
[browser_urlbarRevert.js]
|
||||
skip-if = e10s # Bug 1093941 - ESC reverted the location bar value - Got foobar, expected example.com
|
||||
[browser_urlbarSearchSingleWordNotification.js]
|
||||
skip-if = e10s # Bug 1093997 - intermittent failures in e10s-mode only
|
||||
[browser_urlbarStop.js]
|
||||
skip-if = e10s # Bug 1093941 - test calls gBrowser.contentWindow.stop
|
||||
[browser_urlbarTrimURLs.js]
|
||||
|
@ -479,7 +478,7 @@ skip-if = (os == "win" && !debug) || e10s # Bug 1007418
|
|||
[browser_windowopen_reflows.js]
|
||||
skip-if = buildapp == 'mulet'
|
||||
[browser_wyciwyg_urlbarCopying.js]
|
||||
skip-if = e10s # Bug ?????? - test directly manipulates content (content.document.getElementById)
|
||||
skip-if = e10s # Bug 1100703 - test directly manipulates content (content.document.getElementById)
|
||||
[browser_zbug569342.js]
|
||||
skip-if = e10s # Bug 1094240 - has findbar-related failures
|
||||
[browser_registerProtocolHandler_notification.js]
|
||||
|
@ -491,10 +490,10 @@ skip-if = e10s
|
|||
skip-if = e10s
|
||||
[browser_bug1025195_switchToTabHavingURI_ignoreFragment.js]
|
||||
[browser_addCertException.js]
|
||||
skip-if = e10s # Bug ?????? - test directly manipulates content (content.document.getElementById)
|
||||
skip-if = e10s # Bug 1100687 - test directly manipulates content (content.document.getElementById)
|
||||
[browser_bug1045809.js]
|
||||
[browser_e10s_switchbrowser.js]
|
||||
[browser_blockHPKP.js]
|
||||
skip-if = e10s # bug ?????? - test directly manipulates content (content.document.getElementById)
|
||||
skip-if = e10s # bug 1100687 - test directly manipulates content (content.document.getElementById)
|
||||
[browser_mcb_redirect.js]
|
||||
skip-if = e10s # bug 1084504 - [e10s] Mixed content detection does not take redirection into account
|
||||
|
|
|
@ -2056,25 +2056,23 @@ let CustomizableUIInternal = {
|
|||
// If we're restoring the widget to it's old placement, fire off the
|
||||
// onWidgetAdded event - our own handler will take care of adding it to
|
||||
// any build areas.
|
||||
if (widget.currentArea) {
|
||||
this.notifyListeners("onWidgetAdded", widget.id, widget.currentArea,
|
||||
widget.currentPosition);
|
||||
} else if (widgetMightNeedAutoAdding) {
|
||||
let autoAdd = true;
|
||||
try {
|
||||
autoAdd = Services.prefs.getBoolPref(kPrefCustomizationAutoAdd);
|
||||
} catch (e) {}
|
||||
|
||||
// If the widget doesn't have an existing placement, and it hasn't been
|
||||
// seen before, then add it to its default area so it can be used.
|
||||
// If the widget is not removable, we *have* to add it to its default
|
||||
// area here.
|
||||
let canBeAutoAdded = autoAdd && !gSeenWidgets.has(widget.id);
|
||||
if (!widget.currentArea && (!widget.removable || canBeAutoAdded)) {
|
||||
this.beginBatchUpdate();
|
||||
this.beginBatchUpdate();
|
||||
try {
|
||||
if (widget.currentArea) {
|
||||
this.notifyListeners("onWidgetAdded", widget.id, widget.currentArea,
|
||||
widget.currentPosition);
|
||||
} else if (widgetMightNeedAutoAdding) {
|
||||
let autoAdd = true;
|
||||
try {
|
||||
gSeenWidgets.add(widget.id);
|
||||
autoAdd = Services.prefs.getBoolPref(kPrefCustomizationAutoAdd);
|
||||
} catch (e) {}
|
||||
|
||||
// If the widget doesn't have an existing placement, and it hasn't been
|
||||
// seen before, then add it to its default area so it can be used.
|
||||
// If the widget is not removable, we *have* to add it to its default
|
||||
// area here.
|
||||
let canBeAutoAdded = autoAdd && !gSeenWidgets.has(widget.id);
|
||||
if (!widget.currentArea && (!widget.removable || canBeAutoAdded)) {
|
||||
if (widget.defaultArea) {
|
||||
if (this.isAreaLazy(widget.defaultArea)) {
|
||||
gFuturePlacements.get(widget.defaultArea).add(widget.id);
|
||||
|
@ -2082,10 +2080,13 @@ let CustomizableUIInternal = {
|
|||
this.addWidgetToArea(widget.id, widget.defaultArea);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.endBatchUpdate(true);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Ensure we always have this widget in gSeenWidgets, and save
|
||||
// state in case this needs to be done here.
|
||||
gSeenWidgets.add(widget.id);
|
||||
this.endBatchUpdate(true);
|
||||
}
|
||||
|
||||
this.notifyListeners("onWidgetAfterCreation", widget.id, widget.currentArea);
|
||||
|
@ -2119,7 +2120,7 @@ let CustomizableUIInternal = {
|
|||
normalizeWidget: function(aData, aSource) {
|
||||
let widget = {
|
||||
implementation: aData,
|
||||
source: aSource || "addon",
|
||||
source: aSource || CustomizableUI.SOURCE_EXTERNAL,
|
||||
instances: new Map(),
|
||||
currentArea: null,
|
||||
removable: true,
|
||||
|
@ -2323,6 +2324,15 @@ let CustomizableUIInternal = {
|
|||
// was reset above.
|
||||
this._rebuildRegisteredAreas();
|
||||
|
||||
for (let [widgetId, widget] of gPalette) {
|
||||
if (widget.source == CustomizableUI.SOURCE_EXTERNAL) {
|
||||
gSeenWidgets.add(widgetId);
|
||||
}
|
||||
}
|
||||
if (gSeenWidgets.size) {
|
||||
gDirty = true;
|
||||
}
|
||||
|
||||
gResetting = false;
|
||||
},
|
||||
|
||||
|
|
|
@ -154,3 +154,4 @@ skip-if = os == "mac"
|
|||
[browser_bootstrapped_custom_toolbar.js]
|
||||
[browser_panel_toggle.js]
|
||||
[browser_1089591_still_customizable_after_reset.js]
|
||||
[browser_1096763_seen_widgets_post_reset.js]
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
"use strict";
|
||||
|
||||
const BUTTONID = "test-seenwidget-post-reset";
|
||||
|
||||
add_task(function*() {
|
||||
let widget = CustomizableUI.createWidget({
|
||||
id: BUTTONID,
|
||||
label: "Test widget seen post reset",
|
||||
defaultArea: CustomizableUI.AREA_NAVBAR
|
||||
});
|
||||
|
||||
let bsPass = Cu.import("resource:///modules/CustomizableUI.jsm", {});
|
||||
ok(bsPass.gSeenWidgets.has(BUTTONID), "Widget should be seen after createWidget is called.");
|
||||
CustomizableUI.reset();
|
||||
ok(bsPass.gSeenWidgets.has(BUTTONID), "Widget should still be seen after reset.");
|
||||
ok(!Services.prefs.prefHasUserValue(bsPass.kPrefCustomizationState), "Pref shouldn't be set right now, because that'd break undo.");
|
||||
CustomizableUI.addWidgetToArea(BUTTONID, CustomizableUI.AREA_NAVBAR);
|
||||
gCustomizeMode.removeFromArea(document.getElementById(BUTTONID));
|
||||
let hasUserValue = Services.prefs.prefHasUserValue(bsPass.kPrefCustomizationState);
|
||||
ok(hasUserValue, "Pref should be set right now.");
|
||||
if (hasUserValue) {
|
||||
let seenArray = JSON.parse(Services.prefs.getCharPref(bsPass.kPrefCustomizationState)).seen;
|
||||
isnot(seenArray.indexOf(BUTTONID), -1, "Widget should be in saved 'seen' list.");
|
||||
}
|
||||
});
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
CustomizableUI.destroyWidget(BUTTONID);
|
||||
CustomizableUI.reset();
|
||||
});
|
|
@ -376,6 +376,34 @@ let LoopRoomsInternal = {
|
|||
}, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Renames a room.
|
||||
*
|
||||
* @param {String} roomToken The room token
|
||||
* @param {String} newRoomName The new name for the room
|
||||
* @param {Function} callback Function that will be invoked once the operation
|
||||
* finished. The first argument passed will be an
|
||||
* `Error` object or `null`.
|
||||
*/
|
||||
rename: function(roomToken, newRoomName, callback) {
|
||||
let room = this.rooms.get(roomToken);
|
||||
let url = "/rooms/" + encodeURIComponent(roomToken);
|
||||
|
||||
let origRoom = this.rooms.get(roomToken);
|
||||
let patchData = {
|
||||
roomName: newRoomName,
|
||||
// XXX We have to supply the max size and room owner due to bug 1099063.
|
||||
maxSize: origRoom.maxSize,
|
||||
roomOwner: origRoom.roomOwner
|
||||
};
|
||||
MozLoopService.hawkRequest(this.sessionType, url, "PATCH", patchData)
|
||||
.then(response => {
|
||||
let data = JSON.parse(response.body);
|
||||
extend(room, data);
|
||||
callback(null, room);
|
||||
}, error => callback(error)).catch(error => callback(error));
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback used to indicate changes to rooms data on the LoopServer.
|
||||
*
|
||||
|
@ -443,6 +471,10 @@ this.LoopRooms = {
|
|||
return LoopRoomsInternal.leave(roomToken, sessionToken, callback);
|
||||
},
|
||||
|
||||
rename: function(roomToken, newRoomName, callback) {
|
||||
return LoopRoomsInternal.rename(roomToken, newRoomName, callback);
|
||||
},
|
||||
|
||||
promise: function(method, ...params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this[method](...params, (error, result) => {
|
||||
|
|
|
@ -59,7 +59,7 @@ loop.roomViews = (function(mozL10n) {
|
|||
* Desktop room invitation view (overlay).
|
||||
*/
|
||||
var DesktopRoomInvitationView = React.createClass({displayName: 'DesktopRoomInvitationView',
|
||||
mixins: [ActiveRoomStoreMixin],
|
||||
mixins: [ActiveRoomStoreMixin, React.addons.LinkedStateMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
|
@ -67,13 +67,23 @@ loop.roomViews = (function(mozL10n) {
|
|||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
copiedUrl: false
|
||||
copiedUrl: false,
|
||||
newRoomName: ""
|
||||
}
|
||||
},
|
||||
|
||||
handleFormSubmit: function(event) {
|
||||
event.preventDefault();
|
||||
// XXX
|
||||
|
||||
var newRoomName = this.state.newRoomName;
|
||||
|
||||
if (newRoomName && this.state.roomName != newRoomName) {
|
||||
this.props.dispatcher.dispatch(
|
||||
new sharedActions.RenameRoom({
|
||||
roomToken: this.state.roomToken,
|
||||
newRoomName: newRoomName
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
handleEmailButtonClick: function(event) {
|
||||
|
@ -96,7 +106,9 @@ loop.roomViews = (function(mozL10n) {
|
|||
return (
|
||||
React.DOM.div({className: "room-invitation-overlay"},
|
||||
React.DOM.form({onSubmit: this.handleFormSubmit},
|
||||
React.DOM.input({type: "text", ref: "roomName",
|
||||
React.DOM.input({type: "text", className: "input-room-name",
|
||||
valueLink: this.linkState("newRoomName"),
|
||||
onBlur: this.handleFormSubmit,
|
||||
placeholder: mozL10n.get("rooms_name_this_room_label")})
|
||||
),
|
||||
React.DOM.p(null, mozL10n.get("invite_header_text")),
|
||||
|
|
|
@ -59,7 +59,7 @@ loop.roomViews = (function(mozL10n) {
|
|||
* Desktop room invitation view (overlay).
|
||||
*/
|
||||
var DesktopRoomInvitationView = React.createClass({
|
||||
mixins: [ActiveRoomStoreMixin],
|
||||
mixins: [ActiveRoomStoreMixin, React.addons.LinkedStateMixin],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
|
@ -67,13 +67,23 @@ loop.roomViews = (function(mozL10n) {
|
|||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
copiedUrl: false
|
||||
copiedUrl: false,
|
||||
newRoomName: ""
|
||||
}
|
||||
},
|
||||
|
||||
handleFormSubmit: function(event) {
|
||||
event.preventDefault();
|
||||
// XXX
|
||||
|
||||
var newRoomName = this.state.newRoomName;
|
||||
|
||||
if (newRoomName && this.state.roomName != newRoomName) {
|
||||
this.props.dispatcher.dispatch(
|
||||
new sharedActions.RenameRoom({
|
||||
roomToken: this.state.roomToken,
|
||||
newRoomName: newRoomName
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
handleEmailButtonClick: function(event) {
|
||||
|
@ -96,7 +106,9 @@ loop.roomViews = (function(mozL10n) {
|
|||
return (
|
||||
<div className="room-invitation-overlay">
|
||||
<form onSubmit={this.handleFormSubmit}>
|
||||
<input type="text" ref="roomName"
|
||||
<input type="text" className="input-room-name"
|
||||
valueLink={this.linkState("newRoomName")}
|
||||
onBlur={this.handleFormSubmit}
|
||||
placeholder={mozL10n.get("rooms_name_this_room_label")} />
|
||||
</form>
|
||||
<p>{mozL10n.get("invite_header_text")}</p>
|
||||
|
|
|
@ -242,6 +242,15 @@ loop.shared.actions = (function() {
|
|||
roomToken: String
|
||||
}),
|
||||
|
||||
/**
|
||||
* Renames a room.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
*/
|
||||
RenameRoom: Action.define("renameRoom", {
|
||||
roomToken: String,
|
||||
newRoomName: String
|
||||
}),
|
||||
|
||||
/**
|
||||
* Copy a room url into the user's clipboard.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
|
@ -265,6 +274,19 @@ loop.shared.actions = (function() {
|
|||
error: Object
|
||||
}),
|
||||
|
||||
/**
|
||||
* Sets up the room information when it is received.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
*
|
||||
* @see https://wiki.mozilla.org/Loop/Architecture/Rooms#GET_.2Frooms.2F.7Btoken.7D
|
||||
*/
|
||||
SetupRoomInfo: Action.define("setupRoomInfo", {
|
||||
roomName: String,
|
||||
roomOwner: String,
|
||||
roomToken: String,
|
||||
roomUrl: String
|
||||
}),
|
||||
|
||||
/**
|
||||
* Updates the room information when it is received.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
|
@ -274,7 +296,6 @@ loop.shared.actions = (function() {
|
|||
UpdateRoomInfo: Action.define("updateRoomInfo", {
|
||||
roomName: String,
|
||||
roomOwner: String,
|
||||
roomToken: String,
|
||||
roomUrl: String
|
||||
}),
|
||||
|
||||
|
|
|
@ -150,6 +150,7 @@ loop.store.ActiveRoomStore = (function() {
|
|||
_registerActions: function() {
|
||||
this._dispatcher.register(this, [
|
||||
"roomFailure",
|
||||
"setupRoomInfo",
|
||||
"updateRoomInfo",
|
||||
"joinRoom",
|
||||
"joinedRoom",
|
||||
|
@ -194,12 +195,12 @@ loop.store.ActiveRoomStore = (function() {
|
|||
}
|
||||
|
||||
this._dispatcher.dispatch(
|
||||
new sharedActions.UpdateRoomInfo({
|
||||
roomToken: actionData.roomToken,
|
||||
roomName: roomData.roomName,
|
||||
roomOwner: roomData.roomOwner,
|
||||
roomUrl: roomData.roomUrl
|
||||
}));
|
||||
new sharedActions.SetupRoomInfo({
|
||||
roomToken: actionData.roomToken,
|
||||
roomName: roomData.roomName,
|
||||
roomOwner: roomData.roomOwner,
|
||||
roomUrl: roomData.roomUrl
|
||||
}));
|
||||
|
||||
// For the conversation window, we need to automatically
|
||||
// join the room.
|
||||
|
@ -227,15 +228,18 @@ loop.store.ActiveRoomStore = (function() {
|
|||
roomToken: actionData.token,
|
||||
roomState: ROOM_STATES.READY
|
||||
});
|
||||
|
||||
this._mozLoop.rooms.on("update:" + actionData.roomToken,
|
||||
this._handleRoomUpdate.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the updateRoomInfo action. Updates the room data and
|
||||
* Handles the setupRoomInfo action. Sets up the initial room data and
|
||||
* sets the state to `READY`.
|
||||
*
|
||||
* @param {sharedActions.UpdateRoomInfo} actionData
|
||||
* @param {sharedActions.SetupRoomInfo} actionData
|
||||
*/
|
||||
updateRoomInfo: function(actionData) {
|
||||
setupRoomInfo: function(actionData) {
|
||||
this.setStoreState({
|
||||
roomName: actionData.roomName,
|
||||
roomOwner: actionData.roomOwner,
|
||||
|
@ -243,6 +247,36 @@ loop.store.ActiveRoomStore = (function() {
|
|||
roomToken: actionData.roomToken,
|
||||
roomUrl: actionData.roomUrl
|
||||
});
|
||||
|
||||
this._mozLoop.rooms.on("update:" + actionData.roomToken,
|
||||
this._handleRoomUpdate.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the updateRoomInfo action. Updates the room data.
|
||||
*
|
||||
* @param {sharedActions.UpdateRoomInfo} actionData
|
||||
*/
|
||||
updateRoomInfo: function(actionData) {
|
||||
this.setStoreState({
|
||||
roomName: actionData.roomName,
|
||||
roomOwner: actionData.roomOwner,
|
||||
roomUrl: actionData.roomUrl
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles room updates notified by the mozLoop rooms API.
|
||||
*
|
||||
* @param {String} eventName The name of the event
|
||||
* @param {Object} roomData The new roomData.
|
||||
*/
|
||||
_handleRoomUpdate: function(eventName, roomData) {
|
||||
this._dispatcher.dispatch(new sharedActions.UpdateRoomInfo({
|
||||
roomName: roomData.roomName,
|
||||
roomOwner: roomData.roomOwner,
|
||||
roomUrl: roomData.roomUrl
|
||||
}));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -351,6 +385,10 @@ loop.store.ActiveRoomStore = (function() {
|
|||
*/
|
||||
windowUnload: function() {
|
||||
this._leaveRoom();
|
||||
|
||||
// If we're closing the window, we can stop listening to updates.
|
||||
this._mozLoop.rooms.off("update:" + this.getStoreState().roomToken,
|
||||
this._handleRoomUpdate.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -87,6 +87,7 @@ loop.store = loop.store || {};
|
|||
"getAllRooms",
|
||||
"getAllRoomsError",
|
||||
"openRoom",
|
||||
"renameRoom",
|
||||
"updateRoomList"
|
||||
]);
|
||||
}
|
||||
|
@ -411,6 +412,21 @@ loop.store = loop.store || {};
|
|||
*/
|
||||
openRoom: function(actionData) {
|
||||
this._mozLoop.rooms.open(actionData.roomToken);
|
||||
},
|
||||
|
||||
/**
|
||||
* Renames a room.
|
||||
*
|
||||
* @param {sharedActions.RenameRoom} actionData
|
||||
*/
|
||||
renameRoom: function(actionData) {
|
||||
this._mozLoop.rooms.rename(actionData.roomToken, actionData.newRoomName,
|
||||
function(err) {
|
||||
if (err) {
|
||||
// XXX Give this a proper UI - bug 1100595.
|
||||
console.error("Failed to rename the room", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, Backbone.Events);
|
||||
|
||||
|
|
|
@ -170,7 +170,13 @@ loop.StandaloneMozLoop = (function(mozL10n) {
|
|||
action: "leave",
|
||||
sessionToken: sessionToken
|
||||
}, null, callback);
|
||||
}
|
||||
},
|
||||
|
||||
// Dummy functions to reflect those in the desktop mozLoop.rooms that we
|
||||
// don't currently use.
|
||||
on: function() {},
|
||||
once: function() {},
|
||||
off: function() {}
|
||||
};
|
||||
|
||||
var StandaloneMozLoop = function(options) {
|
||||
|
|
|
@ -119,6 +119,48 @@ describe("loop.roomViews", function () {
|
|||
new sharedActions.EmailRoomUrl({roomUrl: "http://invalid"}));
|
||||
});
|
||||
|
||||
describe("Rename Room", function() {
|
||||
var roomNameBox;
|
||||
|
||||
beforeEach(function() {
|
||||
view = mountTestComponent();
|
||||
view.setState({
|
||||
roomToken: "fakeToken",
|
||||
roomName: "fakeName"
|
||||
});
|
||||
|
||||
roomNameBox = view.getDOMNode().querySelector('.input-room-name');
|
||||
|
||||
React.addons.TestUtils.Simulate.change(roomNameBox, { target: {
|
||||
value: "reallyFake"
|
||||
}});
|
||||
});
|
||||
|
||||
it("should dispatch a RenameRoom action when the focus is lost",
|
||||
function() {
|
||||
React.addons.TestUtils.Simulate.blur(roomNameBox);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.RenameRoom({
|
||||
roomToken: "fakeToken",
|
||||
newRoomName: "reallyFake"
|
||||
}));
|
||||
});
|
||||
|
||||
it("should dispatch a RenameRoom action when enter is pressed",
|
||||
function() {
|
||||
React.addons.TestUtils.Simulate.submit(roomNameBox);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.RenameRoom({
|
||||
roomToken: "fakeToken",
|
||||
newRoomName: "reallyFake"
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Copy Button", function() {
|
||||
beforeEach(function() {
|
||||
view = mountTestComponent();
|
||||
|
|
|
@ -21,10 +21,12 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
|
||||
fakeMozLoop = {
|
||||
rooms: {
|
||||
get: sandbox.stub(),
|
||||
join: sandbox.stub(),
|
||||
refreshMembership: sandbox.stub(),
|
||||
leave: sandbox.stub()
|
||||
get: sinon.stub(),
|
||||
join: sinon.stub(),
|
||||
refreshMembership: sinon.stub(),
|
||||
leave: sinon.stub(),
|
||||
on: sinon.stub(),
|
||||
off: sinon.stub()
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -161,7 +163,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
to.have.property('roomState', ROOM_STATES.GATHER);
|
||||
});
|
||||
|
||||
it("should dispatch an UpdateRoomInfo action if the get is successful",
|
||||
it("should dispatch an SetupRoomInfo action if the get is successful",
|
||||
function() {
|
||||
store.setupWindowData(new sharedActions.SetupWindowData({
|
||||
windowId: "42",
|
||||
|
@ -171,7 +173,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
|
||||
sinon.assert.calledTwice(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo(_.extend({
|
||||
new sharedActions.SetupRoomInfo(_.extend({
|
||||
roomToken: fakeToken
|
||||
}, fakeRoomData)));
|
||||
});
|
||||
|
@ -233,7 +235,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#updateRoomInfo", function() {
|
||||
describe("#setupRoomInfo", function() {
|
||||
var fakeRoomInfo;
|
||||
|
||||
beforeEach(function() {
|
||||
|
@ -246,18 +248,39 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
});
|
||||
|
||||
it("should set the state to READY", function() {
|
||||
store.updateRoomInfo(new sharedActions.UpdateRoomInfo(fakeRoomInfo));
|
||||
store.setupRoomInfo(new sharedActions.SetupRoomInfo(fakeRoomInfo));
|
||||
|
||||
expect(store._storeState.roomState).eql(ROOM_STATES.READY);
|
||||
});
|
||||
|
||||
it("should save the room information", function() {
|
||||
store.setupRoomInfo(new sharedActions.SetupRoomInfo(fakeRoomInfo));
|
||||
|
||||
var state = store.getStoreState();
|
||||
expect(state.roomName).eql(fakeRoomInfo.roomName);
|
||||
expect(state.roomOwner).eql(fakeRoomInfo.roomOwner);
|
||||
expect(state.roomToken).eql(fakeRoomInfo.roomToken);
|
||||
expect(state.roomUrl).eql(fakeRoomInfo.roomUrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#updateRoomInfo", function() {
|
||||
var fakeRoomInfo;
|
||||
|
||||
beforeEach(function() {
|
||||
fakeRoomInfo = {
|
||||
roomName: "Its a room",
|
||||
roomOwner: "Me",
|
||||
roomUrl: "http://invalid"
|
||||
};
|
||||
});
|
||||
|
||||
it("should save the room information", function() {
|
||||
store.updateRoomInfo(new sharedActions.UpdateRoomInfo(fakeRoomInfo));
|
||||
|
||||
var state = store.getStoreState();
|
||||
expect(state.roomName).eql(fakeRoomInfo.roomName);
|
||||
expect(state.roomOwner).eql(fakeRoomInfo.roomOwner);
|
||||
expect(state.roomToken).eql(fakeRoomInfo.roomToken);
|
||||
expect(state.roomUrl).eql(fakeRoomInfo.roomUrl);
|
||||
});
|
||||
});
|
||||
|
@ -596,4 +619,33 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
expect(store._storeState.roomState).eql(ROOM_STATES.READY);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Events", function() {
|
||||
describe("update:{roomToken}", function() {
|
||||
beforeEach(function() {
|
||||
store.setupRoomInfo(new sharedActions.SetupRoomInfo({
|
||||
roomName: "Its a room",
|
||||
roomOwner: "Me",
|
||||
roomToken: "fakeToken",
|
||||
roomUrl: "http://invalid"
|
||||
}));
|
||||
});
|
||||
|
||||
it("should dispatch an UpdateRoomInfo action", function() {
|
||||
sinon.assert.calledOnce(fakeMozLoop.rooms.on);
|
||||
|
||||
var fakeRoomData = {
|
||||
roomName: "fakeName",
|
||||
roomOwner: "you",
|
||||
roomUrl: "original"
|
||||
};
|
||||
|
||||
fakeMozLoop.rooms.on.callArgWith(1, "update", fakeRoomData);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.UpdateRoomInfo(fakeRoomData));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -437,4 +437,31 @@ describe("loop.store.RoomStore", function () {
|
|||
sinon.assert.calledWithExactly(fakeMozLoop.rooms.open, "42abc");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#renameRoom", function() {
|
||||
var store, fakeMozLoop;
|
||||
|
||||
beforeEach(function() {
|
||||
fakeMozLoop = {
|
||||
rooms: {
|
||||
rename: sinon.spy()
|
||||
}
|
||||
};
|
||||
store = new loop.store.RoomStore({
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: fakeMozLoop
|
||||
});
|
||||
});
|
||||
|
||||
it("should rename the room via mozLoop", function() {
|
||||
dispatcher.dispatch(new sharedActions.RenameRoom({
|
||||
roomToken: "42abc",
|
||||
newRoomName: "silly name"
|
||||
}));
|
||||
|
||||
sinon.assert.calledOnce(fakeMozLoop.rooms.rename);
|
||||
sinon.assert.calledWith(fakeMozLoop.rooms.rename, "42abc",
|
||||
"silly name");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -209,16 +209,22 @@ add_task(function* setup_server() {
|
|||
res.finish();
|
||||
}
|
||||
|
||||
function getJSONData(body) {
|
||||
return JSON.parse(CommonUtils.readBytesFromInputStream(body));
|
||||
}
|
||||
|
||||
// Add a request handler for each room in the list.
|
||||
[...kRooms.values()].forEach(function(room) {
|
||||
loopServer.registerPathHandler("/rooms/" + encodeURIComponent(room.roomToken), (req, res) => {
|
||||
if (req.method == "POST") {
|
||||
let body = CommonUtils.readBytesFromInputStream(req.bodyInputStream);
|
||||
let data = JSON.parse(body);
|
||||
let data = getJSONData(req.bodyInputStream);
|
||||
res.setStatusLine(null, 200, "OK");
|
||||
res.write(JSON.stringify(data));
|
||||
res.processAsync();
|
||||
res.finish();
|
||||
} else if (req.method == "PATCH") {
|
||||
let data = getJSONData(req.bodyInputStream);
|
||||
returnRoomDetails(res, data.roomName);
|
||||
} else {
|
||||
returnRoomDetails(res, room.roomName);
|
||||
}
|
||||
|
@ -363,6 +369,13 @@ add_task(function* test_leaveRoom() {
|
|||
Assert.equal(leaveData.sessionToken, "fakeLeaveSessionToken");
|
||||
});
|
||||
|
||||
// Test if renaming a room works as expected.
|
||||
add_task(function* test_renameRoom() {
|
||||
let roomToken = "_nxD4V4FflQ";
|
||||
let renameData = yield LoopRooms.promise("rename", roomToken, "fakeName");
|
||||
Assert.equal(renameData.roomName, "fakeName");
|
||||
});
|
||||
|
||||
// Test if the event emitter implementation doesn't leak and is working as expected.
|
||||
add_task(function* () {
|
||||
Assert.strictEqual(gExpectedAdds.length, 0, "No room additions should be expected anymore");
|
||||
|
|
|
@ -198,14 +198,14 @@ let DebuggerController = {
|
|||
}
|
||||
|
||||
let target = this._target;
|
||||
let { client, form: { chromeDebugger, traceActor, addonActor } } = target;
|
||||
let { client, form: { chromeDebugger, traceActor, actor } } = target;
|
||||
target.on("close", this._onTabDetached);
|
||||
target.on("navigate", this._onTabNavigated);
|
||||
target.on("will-navigate", this._onTabNavigated);
|
||||
this.client = client;
|
||||
|
||||
if (addonActor) {
|
||||
yield this._startAddonDebugging(addonActor);
|
||||
if (target.isAddon) {
|
||||
yield this._startAddonDebugging(actor);
|
||||
} else if (target.chrome) {
|
||||
yield this._startChromeDebugging(chromeDebugger);
|
||||
} else {
|
||||
|
|
|
@ -567,11 +567,7 @@ AddonDebugger.prototype = {
|
|||
let addonActor = yield getAddonActorForUrl(this.client, aUrl);
|
||||
|
||||
let targetOptions = {
|
||||
form: {
|
||||
addonActor: addonActor.actor,
|
||||
consoleActor: addonActor.consoleActor,
|
||||
title: addonActor.name
|
||||
},
|
||||
form: addonActor,
|
||||
client: this.client,
|
||||
chrome: true
|
||||
};
|
||||
|
|
|
@ -154,7 +154,7 @@ let onConnectionReady = Task.async(function*(aType, aTraits) {
|
|||
function buildAddonLink(addon, parent) {
|
||||
let a = document.createElement("a");
|
||||
a.onclick = function() {
|
||||
openToolbox({ addonActor: addon.actor, title: addon.name }, true, "jsdebugger");
|
||||
openToolbox(addon, true, "jsdebugger");
|
||||
}
|
||||
|
||||
a.textContent = addon.name;
|
||||
|
|
|
@ -780,7 +780,7 @@ let gDevToolsBrowser = {
|
|||
|
||||
isWebIDEWidgetInstalled: function() {
|
||||
let widgetWrapper = CustomizableUI.getWidget("webide-button");
|
||||
return !!(widgetWrapper && widgetWrapper.instances.some(i => !!i.node));
|
||||
return !!(widgetWrapper && widgetWrapper.provider == CustomizableUI.PROVIDER_API);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -280,6 +280,13 @@ TabTarget.prototype = {
|
|||
if (!this.client) {
|
||||
throw new Error("TabTarget#getTrait() can only be called on remote tabs.");
|
||||
}
|
||||
|
||||
// If the targeted actor exposes traits and has a defined value for this traits,
|
||||
// override the root actor traits
|
||||
if (this.form.traits && traitName in this.form.traits) {
|
||||
return this.form.traits[traitName];
|
||||
}
|
||||
|
||||
return this.client.traits[traitName];
|
||||
},
|
||||
|
||||
|
@ -323,9 +330,13 @@ TabTarget.prototype = {
|
|||
},
|
||||
|
||||
get name() {
|
||||
return this._tab && this._tab.linkedBrowser.contentDocument ?
|
||||
this._tab.linkedBrowser.contentDocument.title :
|
||||
this._form.title;
|
||||
if (this._tab && this._tab.linkedBrowser.contentDocument) {
|
||||
return this._tab.linkedBrowser.contentDocument.title
|
||||
} else if (this.isAddon) {
|
||||
return this._form.name;
|
||||
} else {
|
||||
return this._form.title;
|
||||
}
|
||||
},
|
||||
|
||||
get url() {
|
||||
|
@ -338,7 +349,8 @@ TabTarget.prototype = {
|
|||
},
|
||||
|
||||
get isAddon() {
|
||||
return !!(this._form && this._form.addonActor);
|
||||
return !!(this._form && this._form.actor &&
|
||||
this._form.actor.match(/conn\d+\.addon\d+/));
|
||||
},
|
||||
|
||||
get isLocalTab() {
|
||||
|
|
|
@ -37,11 +37,7 @@ function connect() {
|
|||
if (addonID) {
|
||||
gClient.listAddons(({addons}) => {
|
||||
let addonActor = addons.filter(addon => addon.id === addonID).pop();
|
||||
openToolbox({
|
||||
addonActor: addonActor.actor,
|
||||
consoleActor: addonActor.consoleActor,
|
||||
title: addonActor.name
|
||||
});
|
||||
openToolbox(addonActor);
|
||||
});
|
||||
} else {
|
||||
gClient.listTabs(openToolbox);
|
||||
|
|
|
@ -53,11 +53,12 @@ loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/
|
|||
// (By default, supported target is only local tab)
|
||||
const ToolboxButtons = [
|
||||
{ id: "command-button-pick",
|
||||
isTargetSupported: target => !target.isAddon },
|
||||
isTargetSupported: target =>
|
||||
target.getTrait("highlightable")
|
||||
},
|
||||
{ id: "command-button-frames",
|
||||
isTargetSupported: target => (
|
||||
!target.isAddon && target.activeTab && target.activeTab.traits.frames
|
||||
)
|
||||
isTargetSupported: target =>
|
||||
( target.activeTab && target.activeTab.traits.frames )
|
||||
},
|
||||
{ id: "command-button-splitconsole",
|
||||
isTargetSupported: target => !target.isAddon },
|
||||
|
@ -1249,12 +1250,15 @@ Toolbox.prototype = {
|
|||
toolName = toolboxStrings("toolbox.defaultTitle");
|
||||
}
|
||||
let title = toolboxStrings("toolbox.titleTemplate",
|
||||
toolName, this.target.url || this.target.name);
|
||||
toolName,
|
||||
this.target.isAddon ?
|
||||
this.target.name :
|
||||
this.target.url || this.target.name);
|
||||
this._host.setTitle(title);
|
||||
},
|
||||
|
||||
_listFrames: function (event) {
|
||||
if (!this._target.form || !this._target.form.actor) {
|
||||
if (!this._target.activeTab || !this._target.activeTab.traits.frames) {
|
||||
// We are not targetting a regular TabActor
|
||||
// it can be either an addon or browser toolbox actor
|
||||
return promise.resolve();
|
||||
|
|
|
@ -117,7 +117,7 @@ Tools.inspector = {
|
|||
},
|
||||
|
||||
isTargetSupported: function(target) {
|
||||
return !target.isAddon && target.hasActor("inspector");
|
||||
return target.hasActor("inspector");
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
|
@ -199,8 +199,7 @@ Tools.styleEditor = {
|
|||
commands: "devtools/styleeditor/styleeditor-commands",
|
||||
|
||||
isTargetSupported: function(target) {
|
||||
return !target.isAddon &&
|
||||
(target.hasActor("styleEditor") || target.hasActor("styleSheets"));
|
||||
return target.hasActor("styleEditor") || target.hasActor("styleSheets");
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
|
@ -220,7 +219,7 @@ Tools.shaderEditor = {
|
|||
tooltip: l10n("ToolboxShaderEditor.tooltip", shaderEditorStrings),
|
||||
|
||||
isTargetSupported: function(target) {
|
||||
return !target.isAddon;
|
||||
return target.hasActor("webgl");
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
|
@ -242,7 +241,7 @@ Tools.canvasDebugger = {
|
|||
// Hide the Canvas Debugger in the Add-on Debugger and Browser Toolbox
|
||||
// (bug 1047520).
|
||||
isTargetSupported: function(target) {
|
||||
return !target.isAddon && !target.chrome;
|
||||
return target.hasActor("canvas") && !target.chrome;
|
||||
},
|
||||
|
||||
build: function (iframeWindow, toolbox) {
|
||||
|
@ -268,7 +267,7 @@ Tools.jsprofiler = {
|
|||
isTargetSupported: function (target) {
|
||||
// Hide the profiler when debugging devices pre bug 1046394,
|
||||
// that don't expose profiler actor in content processes.
|
||||
return !target.isAddon && target.hasActor("profiler");
|
||||
return target.hasActor("profiler");
|
||||
},
|
||||
|
||||
build: function (frame, target) {
|
||||
|
@ -292,7 +291,7 @@ Tools.performance = {
|
|||
inMenu: true,
|
||||
|
||||
isTargetSupported: function (target) {
|
||||
return !target.isAddon && target.hasActor("profiler");
|
||||
return target.hasActor("profiler");
|
||||
},
|
||||
|
||||
build: function (frame, target) {
|
||||
|
@ -312,7 +311,7 @@ Tools.timeline = {
|
|||
tooltip: l10n("timeline.tooltip", timelineStrings),
|
||||
|
||||
isTargetSupported: function(target) {
|
||||
return !target.isAddon && target.hasActor("timeline");
|
||||
return target.hasActor("timeline");
|
||||
},
|
||||
|
||||
build: function (iframeWindow, toolbox) {
|
||||
|
@ -337,7 +336,7 @@ Tools.netMonitor = {
|
|||
inMenu: true,
|
||||
|
||||
isTargetSupported: function(target) {
|
||||
return !target.isAddon && target.getTrait("networkMonitor");
|
||||
return target.getTrait("networkMonitor");
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
|
@ -363,7 +362,8 @@ Tools.storage = {
|
|||
|
||||
isTargetSupported: function(target) {
|
||||
return target.isLocalTab ||
|
||||
(target.client.traits.storageInspector && !target.isAddon);
|
||||
( target.hasActor("storage") &&
|
||||
target.getTrait("storageInspector") );
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
|
@ -383,7 +383,7 @@ Tools.webAudioEditor = {
|
|||
tooltip: l10n("ToolboxWebAudioEditor1.tooltip", webAudioEditorStrings),
|
||||
|
||||
isTargetSupported: function(target) {
|
||||
return !target.isAddon && !target.chrome && target.hasActor("webaudio");
|
||||
return !target.chrome && target.hasActor("webaudio");
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
|
|
|
@ -177,7 +177,6 @@
|
|||
@BINPATH@/components/appstartup.xpt
|
||||
@BINPATH@/components/autocomplete.xpt
|
||||
@BINPATH@/components/autoconfig.xpt
|
||||
@BINPATH@/components/browser-element.xpt
|
||||
@BINPATH@/browser/components/browsercompsbase.xpt
|
||||
@BINPATH@/browser/components/browser-feeds.xpt
|
||||
@BINPATH@/components/caps.xpt
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
|
||||
<!ENTITY locbar.suggest.label "When using the location bar, suggest:">
|
||||
<!ENTITY locbar.history.label "History">
|
||||
<!ENTITY locbar.history.accesskey "i">
|
||||
<!ENTITY locbar.history.accesskey "H">
|
||||
<!ENTITY locbar.bookmarks.label "Bookmarks">
|
||||
<!ENTITY locbar.bookmarks.accesskey "d">
|
||||
<!ENTITY locbar.bookmarks.accesskey "k">
|
||||
<!ENTITY locbar.openpage.label "Open tabs">
|
||||
<!ENTITY locbar.openpage.accesskey "g">
|
||||
<!ENTITY locbar.openpage.accesskey "O">
|
||||
|
||||
<!ENTITY acceptCookies.label "Accept cookies from sites">
|
||||
<!ENTITY acceptCookies.accesskey "A">
|
||||
|
@ -33,7 +33,7 @@
|
|||
<!ENTITY acceptThirdParty.visited.label "From visited">
|
||||
|
||||
<!ENTITY keepUntil.label "Keep until:">
|
||||
<!ENTITY keepUntil.accesskey "K">
|
||||
<!ENTITY keepUntil.accesskey "u">
|
||||
|
||||
<!ENTITY expire.label "they expire">
|
||||
<!ENTITY close.label "I close &brandShortName;">
|
||||
|
|
|
@ -771,8 +771,8 @@
|
|||
0 -2px 0 rgba(0,0,0,.06) inset;
|
||||
}
|
||||
|
||||
#toolbox-tabs .devtools-tab[selected]:not(:first-child),
|
||||
#toolbox-tabs .devtools-tab[highlighted]:not(:first-child) {
|
||||
#toolbox-tabs .devtools-tab[selected],
|
||||
#toolbox-tabs .devtools-tab[highlighted] {
|
||||
border-width: 0;
|
||||
-moz-padding-start: 1px;
|
||||
}
|
||||
|
|
|
@ -43,13 +43,13 @@ let test = Task.async(function*() {
|
|||
docShell.popProfileTimelineMarkers();
|
||||
|
||||
info("Running the test setup function");
|
||||
let onMarkers = waitForMarkers(docShell);
|
||||
let onMarkers = waitForDOMMarkers(docShell, 5);
|
||||
setup();
|
||||
info("Waiting for new markers on the docShell");
|
||||
let markers = yield onMarkers;
|
||||
|
||||
info("Running the test check function");
|
||||
check(markers.filter(m => m.name == "DOMEvent"));
|
||||
check(markers);
|
||||
}
|
||||
|
||||
info("Stop recording");
|
||||
|
@ -73,21 +73,20 @@ function openUrl(url) {
|
|||
});
|
||||
}
|
||||
|
||||
function waitForMarkers(docshell) {
|
||||
function waitForDOMMarkers(docshell, numExpected) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let waitIterationCount = 0;
|
||||
let maxWaitIterationCount = 10; // Wait for 2sec maximum
|
||||
let markers = [];
|
||||
|
||||
let interval = setInterval(() => {
|
||||
let markers = docshell.popProfileTimelineMarkers();
|
||||
if (markers.length > 0) {
|
||||
let newMarkers = docshell.popProfileTimelineMarkers();
|
||||
markers = [...markers, ...newMarkers.filter(m => m.name == "DOMEvent")];
|
||||
if (markers.length >= numExpected
|
||||
|| waitIterationCount > maxWaitIterationCount) {
|
||||
clearInterval(interval);
|
||||
resolve(markers);
|
||||
}
|
||||
if (waitIterationCount > maxWaitIterationCount) {
|
||||
clearInterval(interval);
|
||||
resolve([]);
|
||||
}
|
||||
waitIterationCount++;
|
||||
}, 200);
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function() {
|
||||
// Nothing.
|
||||
dump("ReadyState = " + xhr.readyState + "\n");
|
||||
};
|
||||
xhr.open("get", theURL, true);
|
||||
xhr.send();
|
||||
|
|
|
@ -1,231 +0,0 @@
|
|||
var gWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=widget&getmanifest=true';
|
||||
var gInvalidWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=invalidWidget&getmanifest=true';
|
||||
var gApp;
|
||||
var gHasBrowserPermission;
|
||||
|
||||
function onError() {
|
||||
ok(false, "Error callback invoked");
|
||||
finish();
|
||||
}
|
||||
|
||||
function installApp(path) {
|
||||
var request = navigator.mozApps.install(path);
|
||||
request.onerror = onError;
|
||||
request.onsuccess = function() {
|
||||
gApp = request.result;
|
||||
|
||||
runTest();
|
||||
}
|
||||
}
|
||||
|
||||
function uninstallApp() {
|
||||
// Uninstall the app.
|
||||
var request = navigator.mozApps.mgmt.uninstall(gApp);
|
||||
request.onerror = onError;
|
||||
request.onsuccess = function() {
|
||||
// All done.
|
||||
info("All done");
|
||||
|
||||
runTest();
|
||||
}
|
||||
}
|
||||
|
||||
function testApp(isValidWidget) {
|
||||
info("Test widget feature. IsValidWidget: " + isValidWidget);
|
||||
|
||||
var ifr = document.createElement('iframe');
|
||||
ifr.setAttribute('mozbrowser', 'true');
|
||||
ifr.setAttribute('mozwidget', gApp.manifestURL);
|
||||
ifr.setAttribute('src', gApp.origin+gApp.manifest.launch_path);
|
||||
|
||||
var domParent = document.getElementById('container');
|
||||
domParent.appendChild(ifr);
|
||||
|
||||
var mm = SpecialPowers.getBrowserFrameMessageManager(ifr);
|
||||
mm.addMessageListener('OK', function(msg) {
|
||||
ok(isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
|
||||
});
|
||||
mm.addMessageListener('KO', function(msg) {
|
||||
ok(!isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
|
||||
});
|
||||
mm.addMessageListener('DONE', function(msg) {
|
||||
ok(true, "Message from widget complete: "+SpecialPowers.wrap(msg).json);
|
||||
domParent.removeChild(ifr);
|
||||
runTest();
|
||||
});
|
||||
|
||||
ifr.addEventListener('mozbrowserloadend', function() {
|
||||
ok(true, "receive mozbrowserloadend");
|
||||
|
||||
// Test limited browser API feature only for valid widget case
|
||||
if (isValidWidget) {
|
||||
testLimitedBrowserAPI(ifr);
|
||||
}
|
||||
SimpleTest.executeSoon(()=>loadFrameScript(mm));
|
||||
}, false);
|
||||
|
||||
// Test limited browser API feature only for valid widget case
|
||||
if (!isValidWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
[
|
||||
'mozbrowsertitlechange',
|
||||
'mozbrowseropenwindow',
|
||||
'mozbrowserscroll',
|
||||
'mozbrowserasyncscroll'
|
||||
].forEach( function(topic) {
|
||||
ifr.addEventListener(topic, function() {
|
||||
ok(false, topic + " should be hidden");
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
|
||||
function testLimitedBrowserAPI(ifr) {
|
||||
var securitySensitiveCalls = [
|
||||
{ api: 'sendMouseEvent' , args: ['mousedown', 0, 0, 0, 0, 0] },
|
||||
{ api: 'sendTouchEvent' , args: ['touchstart', [0], [0], [0], [1], [1], [0], [1], 1, 0] },
|
||||
{ api: 'goBack' , args: [] },
|
||||
{ api: 'goForward' , args: [] },
|
||||
{ api: 'reload' , args: [] },
|
||||
{ api: 'stop' , args: [] },
|
||||
{ api: 'download' , args: ['http://example.org'] },
|
||||
{ api: 'purgeHistory' , args: [] },
|
||||
{ api: 'getScreenshot' , args: [0, 0] },
|
||||
{ api: 'zoom' , args: [0.1] },
|
||||
{ api: 'getCanGoBack' , args: [] },
|
||||
{ api: 'getCanGoForward' , args: [] },
|
||||
{ api: 'getContentDimensions', args: [] }
|
||||
];
|
||||
securitySensitiveCalls.forEach( function(call) {
|
||||
if (gHasBrowserPermission) {
|
||||
isnot(typeof ifr[call.api], "undefined", call.api + " should be defined");
|
||||
var didThrow;
|
||||
try {
|
||||
ifr[call.api].apply(ifr, call.args);
|
||||
} catch (e) {
|
||||
ok(e instanceof DOMException, "throw right exception type");
|
||||
didThrow = e.code;
|
||||
}
|
||||
is(didThrow, DOMException.INVALID_NODE_TYPE_ERR, "call " + call.api + " should throw exception");
|
||||
} else {
|
||||
is(typeof ifr[call.api], "undefined", call.api + " should be hidden for widget");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadFrameScript(mm) {
|
||||
var script = 'data:,\
|
||||
function ok(p, msg) { \
|
||||
if (p) { \
|
||||
sendAsyncMessage("OK", msg); \
|
||||
} else { \
|
||||
sendAsyncMessage("KO", msg); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
function is(a, b, msg) { \
|
||||
if (a == b) { \
|
||||
sendAsyncMessage("OK", a + " == " + b + " - " + msg); \
|
||||
} else { \
|
||||
sendAsyncMessage("KO", a + " != " + b + " - " + msg); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
function finish() { \
|
||||
sendAsyncMessage("DONE",""); \
|
||||
} \
|
||||
\
|
||||
function onError() { \
|
||||
ok(false, "Error callback invoked"); \
|
||||
finish(); \
|
||||
} \
|
||||
\
|
||||
function checkWidget(widget) { \
|
||||
/*For invalid widget case, ignore the following check*/\
|
||||
if (widget) { \
|
||||
var widgetName = "Really Rapid Release (APPTYPETOKEN)"; \
|
||||
is(widget.origin, "http://test", "Widget origin should be correct"); \
|
||||
is(widget.installOrigin, "http://mochi.test:8888", "Install origin should be correct"); \
|
||||
} \
|
||||
finish(); \
|
||||
} \
|
||||
\
|
||||
var request = content.window.navigator.mozApps.getSelf(); \
|
||||
request.onsuccess = function() { \
|
||||
var widget = request.result; \
|
||||
ok(widget,"Should be a widget"); \
|
||||
checkWidget(widget); \
|
||||
}; \
|
||||
request.onerror = onError; \
|
||||
content.window.open("about:blank"); /*test mozbrowseropenwindow*/ \
|
||||
content.window.scrollTo(4000, 4000); /*test mozbrowser(async)scroll*/ \
|
||||
';
|
||||
mm.loadFrameScript(script, /* allowDelayedLoad = */ false);
|
||||
}
|
||||
|
||||
var tests = [
|
||||
// Permissions
|
||||
function() {
|
||||
SpecialPowers.pushPermissions(
|
||||
[{ "type": "browser", "allow": gHasBrowserPermission ? 1 : 0, "context": document },
|
||||
{ "type": "embed-widgets", "allow": 1, "context": document },
|
||||
{ "type": "webapps-manage", "allow": 1, "context": document }], runTest);
|
||||
},
|
||||
|
||||
// Preferences
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true],
|
||||
["dom.enable_widgets", true],
|
||||
["dom.datastore.sysMsgOnChangeShortTimeoutSec", 1],
|
||||
["dom.datastore.sysMsgOnChangeLongTimeoutSec", 3]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
if (SpecialPowers.isMainProcess()) {
|
||||
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
|
||||
}
|
||||
|
||||
SpecialPowers.setAllAppsLaunchable(true);
|
||||
runTest();
|
||||
},
|
||||
|
||||
// No confirmation needed when an app is installed
|
||||
function() {
|
||||
SpecialPowers.autoConfirmAppInstall(() => {
|
||||
SpecialPowers.autoConfirmAppUninstall(runTest);
|
||||
});
|
||||
},
|
||||
|
||||
// Installing the app
|
||||
()=>installApp(gWidgetManifestURL),
|
||||
|
||||
// Run tests in app
|
||||
()=>testApp(true),
|
||||
|
||||
// Uninstall the app
|
||||
uninstallApp,
|
||||
|
||||
// Installing the app for invalid widget case
|
||||
()=>installApp(gInvalidWidgetManifestURL),
|
||||
|
||||
// Run tests in app for invalid widget case
|
||||
()=>testApp(false),
|
||||
|
||||
// Uninstall the app
|
||||
uninstallApp
|
||||
];
|
||||
|
||||
function runTest() {
|
||||
if (!tests.length) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var test = tests.shift();
|
||||
test();
|
||||
}
|
||||
|
||||
function finish() {
|
||||
SimpleTest.finish();
|
||||
}
|
|
@ -17,7 +17,6 @@ support-files =
|
|||
file_packaged_app.template.webapp
|
||||
file_widget_app.template.webapp
|
||||
file_widget_app.template.html
|
||||
file_test_widget.js
|
||||
signed_app.sjs
|
||||
signed_app_template.webapp
|
||||
signed/*
|
||||
|
@ -45,5 +44,3 @@ skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
|
|||
[test_web_app_install.html]
|
||||
[test_widget.html]
|
||||
skip-if = os == "android" || toolkit == "gonk" # embed-apps doesn't work in mochitest app
|
||||
[test_widget_browser.html]
|
||||
skip-if = os == "android" || toolkit == "gonk" # embed-apps doesn't work in mochitest app
|
||||
|
|
|
@ -4,14 +4,231 @@
|
|||
<meta charset="utf-8">
|
||||
<title>Test for DataStore - basic operation on a readonly db</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="file_test_widget.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
<script type="application/javascript;version=1.7">
|
||||
|
||||
var gWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=widget&getmanifest=true';
|
||||
var gInvalidWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=invalidWidget&getmanifest=true';
|
||||
var gApp;
|
||||
|
||||
function onError() {
|
||||
ok(false, "Error callback invoked");
|
||||
finish();
|
||||
}
|
||||
|
||||
function installApp(path) {
|
||||
var request = navigator.mozApps.install(path);
|
||||
request.onerror = onError;
|
||||
request.onsuccess = function() {
|
||||
gApp = request.result;
|
||||
|
||||
runTest();
|
||||
}
|
||||
}
|
||||
|
||||
function uninstallApp() {
|
||||
// Uninstall the app.
|
||||
var request = navigator.mozApps.mgmt.uninstall(gApp);
|
||||
request.onerror = onError;
|
||||
request.onsuccess = function() {
|
||||
// All done.
|
||||
info("All done");
|
||||
|
||||
runTest();
|
||||
}
|
||||
}
|
||||
|
||||
function testApp(isValidWidget) {
|
||||
info("Test widget feature. IsValidWidget: " + isValidWidget);
|
||||
|
||||
var ifr = document.createElement('iframe');
|
||||
ifr.setAttribute('mozbrowser', 'true');
|
||||
ifr.setAttribute('mozwidget', gApp.manifestURL);
|
||||
ifr.setAttribute('src', gApp.origin+gApp.manifest.launch_path);
|
||||
|
||||
var domParent = document.getElementById('container');
|
||||
domParent.appendChild(ifr);
|
||||
|
||||
var mm = SpecialPowers.getBrowserFrameMessageManager(ifr);
|
||||
mm.addMessageListener('OK', function(msg) {
|
||||
ok(isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
|
||||
});
|
||||
mm.addMessageListener('KO', function(msg) {
|
||||
ok(!isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
|
||||
});
|
||||
mm.addMessageListener('DONE', function(msg) {
|
||||
ok(true, "Message from widget complete: "+SpecialPowers.wrap(msg).json);
|
||||
domParent.removeChild(ifr);
|
||||
runTest();
|
||||
});
|
||||
|
||||
ifr.addEventListener('mozbrowserloadend', function() {
|
||||
ok(true, "receive mozbrowserloadend");
|
||||
|
||||
// Test limited browser API feature only for valid widget case
|
||||
if (isValidWidget) {
|
||||
testLimitedBrowserAPI(ifr);
|
||||
}
|
||||
SimpleTest.executeSoon(()=>loadFrameScript(mm));
|
||||
}, false);
|
||||
|
||||
// Test limited browser API feature only for valid widget case
|
||||
if (!isValidWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
[
|
||||
'mozbrowsertitlechange',
|
||||
'mozbrowseropenwindow',
|
||||
'mozbrowserscroll',
|
||||
'mozbrowserasyncscroll'
|
||||
].forEach( function(topic) {
|
||||
ifr.addEventListener(topic, function() {
|
||||
ok(false, topic + " should be hidden");
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
|
||||
function testLimitedBrowserAPI(ifr) {
|
||||
var securitySensitiveCalls = [
|
||||
'sendMouseEvent',
|
||||
'sendTouchEvent',
|
||||
'goBack',
|
||||
'goForward',
|
||||
'reload',
|
||||
'stop',
|
||||
'download',
|
||||
'purgeHistory',
|
||||
'getScreenshot',
|
||||
'zoom',
|
||||
'getCanGoBack',
|
||||
'getCanGoForward'
|
||||
];
|
||||
securitySensitiveCalls.forEach( function(call) {
|
||||
is(typeof ifr[call], "undefined", call + " should be hidden for widget");
|
||||
});
|
||||
}
|
||||
|
||||
function loadFrameScript(mm) {
|
||||
var script = 'data:,\
|
||||
function ok(p, msg) { \
|
||||
if (p) { \
|
||||
sendAsyncMessage("OK", msg); \
|
||||
} else { \
|
||||
sendAsyncMessage("KO", msg); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
function is(a, b, msg) { \
|
||||
if (a == b) { \
|
||||
sendAsyncMessage("OK", a + " == " + b + " - " + msg); \
|
||||
} else { \
|
||||
sendAsyncMessage("KO", a + " != " + b + " - " + msg); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
function finish() { \
|
||||
sendAsyncMessage("DONE",""); \
|
||||
} \
|
||||
\
|
||||
function onError() { \
|
||||
ok(false, "Error callback invoked"); \
|
||||
finish(); \
|
||||
} \
|
||||
\
|
||||
function checkWidget(widget) { \
|
||||
/*For invalid widget case, ignore the following check*/\
|
||||
if (widget) { \
|
||||
var widgetName = "Really Rapid Release (APPTYPETOKEN)"; \
|
||||
is(widget.origin, "http://test", "Widget origin should be correct"); \
|
||||
is(widget.installOrigin, "http://mochi.test:8888", "Install origin should be correct"); \
|
||||
} \
|
||||
finish(); \
|
||||
} \
|
||||
\
|
||||
var request = content.window.navigator.mozApps.getSelf(); \
|
||||
request.onsuccess = function() { \
|
||||
var widget = request.result; \
|
||||
ok(widget,"Should be a widget"); \
|
||||
checkWidget(widget); \
|
||||
}; \
|
||||
request.onerror = onError; \
|
||||
content.window.open("about:blank"); /*test mozbrowseropenwindow*/ \
|
||||
content.window.scrollTo(4000, 4000); /*test mozbrowser(async)scroll*/ \
|
||||
';
|
||||
mm.loadFrameScript(script, /* allowDelayedLoad = */ false);
|
||||
}
|
||||
|
||||
var tests = [
|
||||
// Permissions
|
||||
function() {
|
||||
SpecialPowers.pushPermissions(
|
||||
[{ "type": "browser", "allow": 1, "context": document },
|
||||
{ "type": "embed-widgets", "allow": 1, "context": document },
|
||||
{ "type": "webapps-manage", "allow": 1, "context": document }], runTest);
|
||||
},
|
||||
|
||||
// Preferences
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true],
|
||||
["dom.enable_widgets", true],
|
||||
["dom.datastore.sysMsgOnChangeShortTimeoutSec", 1],
|
||||
["dom.datastore.sysMsgOnChangeLongTimeoutSec", 3]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
if (SpecialPowers.isMainProcess()) {
|
||||
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
|
||||
}
|
||||
|
||||
SpecialPowers.setAllAppsLaunchable(true);
|
||||
runTest();
|
||||
},
|
||||
|
||||
// No confirmation needed when an app is installed
|
||||
function() {
|
||||
SpecialPowers.autoConfirmAppInstall(() => {
|
||||
SpecialPowers.autoConfirmAppUninstall(runTest);
|
||||
});
|
||||
},
|
||||
|
||||
// Installing the app
|
||||
()=>installApp(gWidgetManifestURL),
|
||||
|
||||
// Run tests in app
|
||||
()=>testApp(true),
|
||||
|
||||
// Uninstall the app
|
||||
uninstallApp,
|
||||
|
||||
// Installing the app for invalid widget case
|
||||
()=>installApp(gInvalidWidgetManifestURL),
|
||||
|
||||
// Run tests in app for invalid widget case
|
||||
()=>testApp(false),
|
||||
|
||||
// Uninstall the app
|
||||
uninstallApp
|
||||
];
|
||||
|
||||
function runTest() {
|
||||
if (!tests.length) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var test = tests.shift();
|
||||
test();
|
||||
}
|
||||
|
||||
function finish() {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
gHasBrowserPermission = false;
|
||||
runTest();
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for DataStore - basic operation on a readonly db</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="file_test_widget.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
<script type="application/javascript;version=1.7">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
gHasBrowserPermission = true;
|
||||
runTest();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -4,910 +4,126 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
let Cu = Components.utils;
|
||||
let Ci = Components.interfaces;
|
||||
let Cc = Components.classes;
|
||||
let Cr = Components.results;
|
||||
|
||||
/* BrowserElementParent injects script to listen for certain events in the
|
||||
* child. We then listen to messages from the child script and take
|
||||
* appropriate action here in the parent.
|
||||
*/
|
||||
const {utils: Cu, interfaces: Ci} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function () {
|
||||
Cu.import("resource://gre/modules/Webapps.jsm");
|
||||
return DOMApplicationRegistry;
|
||||
});
|
||||
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
|
||||
const BROWSER_FRAMES_ENABLED_PREF = "dom.mozBrowserFramesEnabled";
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "BrowserElementParentBuilder",
|
||||
"resource://gre/modules/BrowserElementParent.jsm",
|
||||
"BrowserElementParentBuilder");
|
||||
|
||||
function debug(msg) {
|
||||
//dump("BrowserElementParent - " + msg + "\n");
|
||||
//dump("BrowserElementParent.js - " + msg + "\n");
|
||||
}
|
||||
|
||||
function getIntPref(prefName, def) {
|
||||
try {
|
||||
return Services.prefs.getIntPref(prefName);
|
||||
}
|
||||
catch(err) {
|
||||
return def;
|
||||
}
|
||||
/**
|
||||
* BrowserElementParent implements one half of <iframe mozbrowser>. (The other
|
||||
* half is, unsurprisingly, BrowserElementChild.)
|
||||
*
|
||||
* BrowserElementParentFactory detects when we create a windows or docshell
|
||||
* contained inside a <iframe mozbrowser> and creates a BrowserElementParent
|
||||
* object for that window.
|
||||
*
|
||||
* It creates a BrowserElementParent that injects script to listen for
|
||||
* certain event.
|
||||
*/
|
||||
|
||||
function BrowserElementParentFactory() {
|
||||
this._initialized = false;
|
||||
}
|
||||
|
||||
function visibilityChangeHandler(e) {
|
||||
// The visibilitychange event's target is the document.
|
||||
let win = e.target.defaultView;
|
||||
|
||||
if (!win._browserElementParents) {
|
||||
return;
|
||||
}
|
||||
|
||||
let beps = Cu.nondeterministicGetWeakMapKeys(win._browserElementParents);
|
||||
if (beps.length == 0) {
|
||||
win.removeEventListener('visibilitychange', visibilityChangeHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < beps.length; i++) {
|
||||
beps[i]._ownerVisibilityChange();
|
||||
}
|
||||
}
|
||||
|
||||
function defineNoReturnMethod(fn) {
|
||||
return function method() {
|
||||
if (!this._domRequestReady) {
|
||||
// Remote browser haven't been created, we just queue the API call.
|
||||
let args = Array.slice(arguments);
|
||||
args.unshift(this);
|
||||
this._pendingAPICalls.push(method.bind.apply(fn, args));
|
||||
return;
|
||||
}
|
||||
if (this._isAlive()) {
|
||||
fn.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function defineDOMRequestMethod(msgName) {
|
||||
return function() {
|
||||
return this._sendDOMRequest(msgName);
|
||||
};
|
||||
}
|
||||
|
||||
function BrowserElementParent() {
|
||||
debug("Creating new BrowserElementParent object");
|
||||
this._domRequestCounter = 0;
|
||||
this._domRequestReady = false;
|
||||
this._pendingAPICalls = [];
|
||||
this._pendingDOMRequests = {};
|
||||
this._pendingSetInputMethodActive = [];
|
||||
this._nextPaintListeners = [];
|
||||
|
||||
Services.obs.addObserver(this, 'ask-children-to-exit-fullscreen', /* ownsWeak = */ true);
|
||||
Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
|
||||
Services.obs.addObserver(this, 'copypaste-docommand', /* ownsWeak = */ true);
|
||||
}
|
||||
|
||||
BrowserElementParent.prototype = {
|
||||
|
||||
classDescription: "BrowserElementAPI implementation",
|
||||
classID: Components.ID("{9f171ac4-0939-4ef8-b360-3408aedc3060}"),
|
||||
contractID: "@mozilla.org/dom/browser-element-api;1",
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserElementAPI,
|
||||
Ci.nsIObserver,
|
||||
BrowserElementParentFactory.prototype = {
|
||||
classID: Components.ID("{ddeafdac-cb39-47c4-9cb8-c9027ee36d26}"),
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
setFrameLoader: function(frameLoader) {
|
||||
this._frameLoader = frameLoader;
|
||||
this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
|
||||
if (!this._frameElement) {
|
||||
debug("No frame element?");
|
||||
return;
|
||||
}
|
||||
// Listen to visibilitychange on the iframe's owner window, and forward
|
||||
// changes down to the child. We want to do this while registering as few
|
||||
// visibilitychange listeners on _window as possible, because such a listener
|
||||
// may live longer than this BrowserElementParent object.
|
||||
//
|
||||
// To accomplish this, we register just one listener on the window, and have
|
||||
// it reference a WeakMap whose keys are all the BrowserElementParent objects
|
||||
// on the window. Then when the listener fires, we iterate over the
|
||||
// WeakMap's keys (which we can do, because we're chrome) to notify the
|
||||
// BrowserElementParents.
|
||||
if (!this._window._browserElementParents) {
|
||||
this._window._browserElementParents = new WeakMap();
|
||||
this._window.addEventListener('visibilitychange',
|
||||
visibilityChangeHandler,
|
||||
/* useCapture = */ false,
|
||||
/* wantsUntrusted = */ false);
|
||||
}
|
||||
|
||||
this._window._browserElementParents.set(this, null);
|
||||
|
||||
// Insert ourself into the prompt service.
|
||||
BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
|
||||
this._setupMessageListener();
|
||||
this._registerAppManifest();
|
||||
},
|
||||
|
||||
_runPendingAPICall: function() {
|
||||
if (!this._pendingAPICalls) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < this._pendingAPICalls.length; i++) {
|
||||
try {
|
||||
this._pendingAPICalls[i]();
|
||||
} catch (e) {
|
||||
// throw the expections from pending functions.
|
||||
debug('Exception when running pending API call: ' + e);
|
||||
}
|
||||
}
|
||||
delete this._pendingAPICalls;
|
||||
},
|
||||
|
||||
_registerAppManifest: function() {
|
||||
// If this browser represents an app then let the Webapps module register for
|
||||
// any messages that it needs.
|
||||
let appManifestURL =
|
||||
this._frameElement.QueryInterface(Ci.nsIMozBrowserFrame).appManifestURL;
|
||||
if (appManifestURL) {
|
||||
let inParent = Cc["@mozilla.org/xre/app-info;1"]
|
||||
.getService(Ci.nsIXULRuntime)
|
||||
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
|
||||
if (inParent) {
|
||||
DOMApplicationRegistry.registerBrowserElementParentForApp(
|
||||
{ manifestURL: appManifestURL }, this._mm);
|
||||
} else {
|
||||
this._mm.sendAsyncMessage("Webapps:RegisterBEP",
|
||||
{ manifestURL: appManifestURL });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_setupMessageListener: function() {
|
||||
this._mm = this._frameLoader.messageManager;
|
||||
let self = this;
|
||||
let isWidget = this._frameLoader
|
||||
.QueryInterface(Ci.nsIFrameLoader)
|
||||
.ownerIsWidget;
|
||||
|
||||
// Messages we receive are handed to functions which take a (data) argument,
|
||||
// where |data| is the message manager's data object.
|
||||
// We use a single message and dispatch to various function based
|
||||
// on data.msg_name
|
||||
let mmCalls = {
|
||||
"hello": this._recvHello,
|
||||
"loadstart": this._fireProfiledEventFromMsg,
|
||||
"loadend": this._fireProfiledEventFromMsg,
|
||||
"close": this._fireEventFromMsg,
|
||||
"error": this._fireEventFromMsg,
|
||||
"firstpaint": this._fireProfiledEventFromMsg,
|
||||
"documentfirstpaint": this._fireProfiledEventFromMsg,
|
||||
"nextpaint": this._recvNextPaint,
|
||||
"got-purge-history": this._gotDOMRequestResult,
|
||||
"got-screenshot": this._gotDOMRequestResult,
|
||||
"got-contentdimensions": this._gotDOMRequestResult,
|
||||
"got-can-go-back": this._gotDOMRequestResult,
|
||||
"got-can-go-forward": this._gotDOMRequestResult,
|
||||
"fullscreen-origin-change": this._remoteFullscreenOriginChange,
|
||||
"rollback-fullscreen": this._remoteFrameFullscreenReverted,
|
||||
"exit-fullscreen": this._exitFullscreen,
|
||||
"got-visible": this._gotDOMRequestResult,
|
||||
"visibilitychange": this._childVisibilityChange,
|
||||
"got-set-input-method-active": this._gotDOMRequestResult,
|
||||
"selectionchange": this._handleSelectionChange,
|
||||
"scrollviewchange": this._handleScrollViewChange,
|
||||
"touchcarettap": this._handleTouchCaretTap
|
||||
};
|
||||
|
||||
let mmSecuritySensitiveCalls = {
|
||||
"showmodalprompt": this._handleShowModalPrompt,
|
||||
"contextmenu": this._fireCtxMenuEvent,
|
||||
"securitychange": this._fireEventFromMsg,
|
||||
"locationchange": this._fireEventFromMsg,
|
||||
"iconchange": this._fireEventFromMsg,
|
||||
"scrollareachanged": this._fireEventFromMsg,
|
||||
"titlechange": this._fireProfiledEventFromMsg,
|
||||
"opensearch": this._fireEventFromMsg,
|
||||
"manifestchange": this._fireEventFromMsg,
|
||||
"metachange": this._fireEventFromMsg,
|
||||
"resize": this._fireEventFromMsg,
|
||||
"activitydone": this._fireEventFromMsg,
|
||||
"scroll": this._fireEventFromMsg
|
||||
};
|
||||
|
||||
this._mm.addMessageListener('browser-element-api:call', function(aMsg) {
|
||||
if (!self._isAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aMsg.data.msg_name in mmCalls) {
|
||||
return mmCalls[aMsg.data.msg_name].apply(self, arguments);
|
||||
} else if (!isWidget && aMsg.data.msg_name in mmSecuritySensitiveCalls) {
|
||||
return mmSecuritySensitiveCalls[aMsg.data.msg_name]
|
||||
.apply(self, arguments);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* You shouldn't touch this._frameElement or this._window if _isAlive is
|
||||
* false. (You'll likely get an exception if you do.)
|
||||
* Called on app startup, and also when the browser frames enabled pref is
|
||||
* changed.
|
||||
*/
|
||||
_isAlive: function() {
|
||||
return !Cu.isDeadWrapper(this._frameElement) &&
|
||||
!Cu.isDeadWrapper(this._frameElement.ownerDocument) &&
|
||||
!Cu.isDeadWrapper(this._frameElement.ownerDocument.defaultView);
|
||||
},
|
||||
|
||||
get _window() {
|
||||
return this._frameElement.ownerDocument.defaultView;
|
||||
},
|
||||
|
||||
get _windowUtils() {
|
||||
return this._window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
},
|
||||
|
||||
promptAuth: function(authDetail, callback) {
|
||||
let evt;
|
||||
let self = this;
|
||||
let callbackCalled = false;
|
||||
let cancelCallback = function() {
|
||||
if (!callbackCalled) {
|
||||
callbackCalled = true;
|
||||
callback(false, null, null);
|
||||
}
|
||||
};
|
||||
|
||||
// 1. We don't handle password-only prompts.
|
||||
// 2. We don't handle for widget case because of security concern.
|
||||
if (authDetail.isOnlyPassword ||
|
||||
this._frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsWidget) {
|
||||
cancelCallback();
|
||||
_init: function() {
|
||||
if (this._initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* username and password */
|
||||
let detail = {
|
||||
host: authDetail.host,
|
||||
realm: authDetail.realm
|
||||
};
|
||||
|
||||
evt = this._createEvent('usernameandpasswordrequired', detail,
|
||||
/* cancelable */ true);
|
||||
Cu.exportFunction(function(username, password) {
|
||||
if (callbackCalled)
|
||||
return;
|
||||
callbackCalled = true;
|
||||
callback(true, username, password);
|
||||
}, evt.detail, { defineAs: 'authenticate' });
|
||||
|
||||
Cu.exportFunction(cancelCallback, evt.detail, { defineAs: 'cancel' });
|
||||
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
|
||||
if (!evt.defaultPrevented) {
|
||||
cancelCallback();
|
||||
// If the pref is disabled, do nothing except wait for the pref to change.
|
||||
// (This is important for tests, if nothing else.)
|
||||
if (!this._browserFramesPrefEnabled()) {
|
||||
Services.prefs.addObserver(BROWSER_FRAMES_ENABLED_PREF, this, /* ownsWeak = */ true);
|
||||
return;
|
||||
}
|
||||
|
||||
debug("_init");
|
||||
this._initialized = true;
|
||||
|
||||
// Maps frame elements to BrowserElementParent objects. We never look up
|
||||
// anything in this map; the purpose is to keep the BrowserElementParent
|
||||
// alive for as long as its frame element lives.
|
||||
this._bepMap = new WeakMap();
|
||||
|
||||
Services.obs.addObserver(this, 'remote-browser-pending', /* ownsWeak = */ true);
|
||||
Services.obs.addObserver(this, 'inprocess-browser-shown', /* ownsWeak = */ true);
|
||||
},
|
||||
|
||||
_sendAsyncMsg: function(msg, data) {
|
||||
_browserFramesPrefEnabled: function() {
|
||||
try {
|
||||
if (!data) {
|
||||
data = { };
|
||||
}
|
||||
|
||||
data.msg_name = msg;
|
||||
this._mm.sendAsyncMessage('browser-element-api:call', data);
|
||||
} catch (e) {
|
||||
return Services.prefs.getBoolPref(BROWSER_FRAMES_ENABLED_PREF);
|
||||
}
|
||||
catch(e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_recvHello: function() {
|
||||
debug("recvHello");
|
||||
|
||||
// Inform our child if our owner element's document is invisible. Note
|
||||
// that we must do so here, rather than in the BrowserElementParent
|
||||
// constructor, because the BrowserElementChild may not be initialized when
|
||||
// we run our constructor.
|
||||
if (this._window.document.hidden) {
|
||||
this._ownerVisibilityChange();
|
||||
_observeInProcessBrowserFrameShown: function(frameLoader) {
|
||||
// Ignore notifications that aren't from a BrowserOrApp
|
||||
if (!frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsBrowserOrAppFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._domRequestReady) {
|
||||
// At least, one message listener such as for hello is registered.
|
||||
// So we can use sendAsyncMessage now.
|
||||
this._domRequestReady = true;
|
||||
this._runPendingAPICall();
|
||||
}
|
||||
|
||||
return {
|
||||
name: this._frameElement.getAttribute('name'),
|
||||
fullscreenAllowed:
|
||||
this._frameElement.hasAttribute('allowfullscreen') ||
|
||||
this._frameElement.hasAttribute('mozallowfullscreen'),
|
||||
isPrivate: this._frameElement.hasAttribute('mozprivatebrowsing')
|
||||
};
|
||||
debug("In-process browser frame shown " + frameLoader);
|
||||
this._createBrowserElementParent(frameLoader,
|
||||
/* hasRemoteFrame = */ false,
|
||||
/* pending frame */ false);
|
||||
},
|
||||
|
||||
_fireCtxMenuEvent: function(data) {
|
||||
let detail = data.json;
|
||||
let evtName = detail.msg_name;
|
||||
|
||||
debug('fireCtxMenuEventFromMsg: ' + evtName + ' ' + detail);
|
||||
let evt = this._createEvent(evtName, detail, /* cancellable */ true);
|
||||
|
||||
if (detail.contextmenu) {
|
||||
var self = this;
|
||||
Cu.exportFunction(function(id) {
|
||||
self._sendAsyncMsg('fire-ctx-callback', {menuitem: id});
|
||||
}, evt.detail, { defineAs: 'contextMenuItemSelected' });
|
||||
_observeRemoteBrowserFramePending: function(frameLoader) {
|
||||
// Ignore notifications that aren't from a BrowserOrApp
|
||||
if (!frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsBrowserOrAppFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The embedder may have default actions on context menu events, so
|
||||
// we fire a context menu event even if the child didn't define a
|
||||
// custom context menu
|
||||
return !this._frameElement.dispatchEvent(evt);
|
||||
debug("Remote browser frame shown " + frameLoader);
|
||||
this._createBrowserElementParent(frameLoader,
|
||||
/* hasRemoteFrame = */ true,
|
||||
/* pending frame */ true);
|
||||
},
|
||||
|
||||
/**
|
||||
* add profiler marker for each event fired.
|
||||
*/
|
||||
_fireProfiledEventFromMsg: function(data) {
|
||||
if (Services.profiler !== undefined) {
|
||||
Services.profiler.AddMarker(data.json.msg_name);
|
||||
}
|
||||
this._fireEventFromMsg(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fire either a vanilla or a custom event, depending on the contents of
|
||||
* |data|.
|
||||
*/
|
||||
_fireEventFromMsg: function(data) {
|
||||
let detail = data.json;
|
||||
let name = detail.msg_name;
|
||||
|
||||
// For events that send a "_payload_" property, we just want to transmit
|
||||
// this in the event.
|
||||
if ("_payload_" in detail) {
|
||||
detail = detail._payload_;
|
||||
}
|
||||
|
||||
debug('fireEventFromMsg: ' + name + ', ' + JSON.stringify(detail));
|
||||
let evt = this._createEvent(name, detail,
|
||||
/* cancelable = */ false);
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
_handleShowModalPrompt: function(data) {
|
||||
// Fire a showmodalprmopt event on the iframe. When this method is called,
|
||||
// the child is spinning in a nested event loop waiting for an
|
||||
// unblock-modal-prompt message.
|
||||
//
|
||||
// If the embedder calls preventDefault() on the showmodalprompt event,
|
||||
// we'll block the child until event.detail.unblock() is called.
|
||||
//
|
||||
// Otherwise, if preventDefault() is not called, we'll send the
|
||||
// unblock-modal-prompt message to the child as soon as the event is done
|
||||
// dispatching.
|
||||
|
||||
let detail = data.json;
|
||||
debug('handleShowPrompt ' + JSON.stringify(detail));
|
||||
|
||||
// Strip off the windowID property from the object we send along in the
|
||||
// event.
|
||||
let windowID = detail.windowID;
|
||||
delete detail.windowID;
|
||||
debug("Event will have detail: " + JSON.stringify(detail));
|
||||
let evt = this._createEvent('showmodalprompt', detail,
|
||||
/* cancelable = */ true);
|
||||
|
||||
let self = this;
|
||||
let unblockMsgSent = false;
|
||||
function sendUnblockMsg() {
|
||||
if (unblockMsgSent) {
|
||||
return;
|
||||
}
|
||||
unblockMsgSent = true;
|
||||
|
||||
// We don't need to sanitize evt.detail.returnValue (e.g. converting the
|
||||
// return value of confirm() to a boolean); Gecko does that for us.
|
||||
|
||||
let data = { windowID: windowID,
|
||||
returnValue: evt.detail.returnValue };
|
||||
self._sendAsyncMsg('unblock-modal-prompt', data);
|
||||
}
|
||||
|
||||
Cu.exportFunction(sendUnblockMsg, evt.detail, { defineAs: 'unblock' });
|
||||
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
|
||||
if (!evt.defaultPrevented) {
|
||||
// Unblock the inner frame immediately. Otherwise we'll unblock upon
|
||||
// evt.detail.unblock().
|
||||
sendUnblockMsg();
|
||||
}
|
||||
},
|
||||
|
||||
_handleSelectionChange: function(data) {
|
||||
let evt = this._createEvent('selectionchange', data.json,
|
||||
/* cancelable = */ false);
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
_handleScrollViewChange: function(data) {
|
||||
let evt = this._createEvent("scrollviewchange", data.json,
|
||||
/* cancelable = */ false);
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
_handleTouchCaretTap: function(data) {
|
||||
let evt = this._createEvent("touchcarettap", data.json,
|
||||
/* cancelable = */ false);
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
_createEvent: function(evtName, detail, cancelable) {
|
||||
// This will have to change if we ever want to send a CustomEvent with null
|
||||
// detail. For now, it's OK.
|
||||
if (detail !== undefined && detail !== null) {
|
||||
detail = Cu.cloneInto(detail, this._window);
|
||||
return new this._window.CustomEvent('mozbrowser' + evtName,
|
||||
{ bubbles: true,
|
||||
cancelable: cancelable,
|
||||
detail: detail });
|
||||
}
|
||||
|
||||
return new this._window.Event('mozbrowser' + evtName,
|
||||
{ bubbles: true,
|
||||
cancelable: cancelable });
|
||||
},
|
||||
|
||||
/**
|
||||
* Kick off a DOMRequest in the child process.
|
||||
*
|
||||
* We'll fire an event called |msgName| on the child process, passing along
|
||||
* an object with two fields:
|
||||
*
|
||||
* - id: the ID of this request.
|
||||
* - arg: arguments to pass to the child along with this request.
|
||||
*
|
||||
* We expect the child to pass the ID back to us upon completion of the
|
||||
* request. See _gotDOMRequestResult.
|
||||
*/
|
||||
_sendDOMRequest: function(msgName, args) {
|
||||
let id = 'req_' + this._domRequestCounter++;
|
||||
let req = Services.DOMRequest.createRequest(this._window);
|
||||
let self = this;
|
||||
let send = function() {
|
||||
if (!self._isAlive()) {
|
||||
return;
|
||||
}
|
||||
if (self._sendAsyncMsg(msgName, {id: id, args: args})) {
|
||||
self._pendingDOMRequests[id] = req;
|
||||
} else {
|
||||
Services.DOMRequest.fireErrorAsync(req, "fail");
|
||||
}
|
||||
};
|
||||
if (this._domRequestReady) {
|
||||
send();
|
||||
} else {
|
||||
// Child haven't been loaded.
|
||||
this._pendingAPICalls.push(send);
|
||||
}
|
||||
return req;
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the child process finishes handling a DOMRequest. data.json
|
||||
* must have the fields [id, successRv], if the DOMRequest was successful, or
|
||||
* [id, errorMsg], if the request was not successful.
|
||||
*
|
||||
* The fields have the following meanings:
|
||||
*
|
||||
* - id: the ID of the DOM request (see _sendDOMRequest)
|
||||
* - successRv: the request's return value, if the request succeeded
|
||||
* - errorMsg: the message to pass to DOMRequest.fireError(), if the request
|
||||
* failed.
|
||||
*
|
||||
*/
|
||||
_gotDOMRequestResult: function(data) {
|
||||
let req = this._pendingDOMRequests[data.json.id];
|
||||
delete this._pendingDOMRequests[data.json.id];
|
||||
|
||||
if ('successRv' in data.json) {
|
||||
debug("Successful gotDOMRequestResult.");
|
||||
let clientObj = Cu.cloneInto(data.json.successRv, this._window);
|
||||
Services.DOMRequest.fireSuccess(req, clientObj);
|
||||
}
|
||||
else {
|
||||
debug("Got error in gotDOMRequestResult.");
|
||||
Services.DOMRequest.fireErrorAsync(req,
|
||||
Cu.cloneInto(data.json.errorMsg, this._window));
|
||||
}
|
||||
},
|
||||
|
||||
setVisible: defineNoReturnMethod(function(visible) {
|
||||
this._sendAsyncMsg('set-visible', {visible: visible});
|
||||
this._frameLoader.visible = visible;
|
||||
}),
|
||||
|
||||
getVisible: defineDOMRequestMethod('get-visible'),
|
||||
|
||||
setActive: defineNoReturnMethod(function(active) {
|
||||
this._frameLoader.visible = active;
|
||||
}),
|
||||
|
||||
getActive: function() {
|
||||
if (!this._isAlive()) {
|
||||
throw Components.Exception("Dead content process",
|
||||
Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
|
||||
return this._frameLoader.visible;
|
||||
},
|
||||
|
||||
sendMouseEvent: defineNoReturnMethod(function(type, x, y, button, clickCount, modifiers) {
|
||||
this._sendAsyncMsg("send-mouse-event", {
|
||||
"type": type,
|
||||
"x": x,
|
||||
"y": y,
|
||||
"button": button,
|
||||
"clickCount": clickCount,
|
||||
"modifiers": modifiers
|
||||
});
|
||||
}),
|
||||
|
||||
sendTouchEvent: defineNoReturnMethod(function(type, identifiers, touchesX, touchesY,
|
||||
radiisX, radiisY, rotationAngles, forces,
|
||||
count, modifiers) {
|
||||
|
||||
let tabParent = this._frameLoader.tabParent;
|
||||
if (tabParent && tabParent.useAsyncPanZoom) {
|
||||
tabParent.injectTouchEvent(type,
|
||||
identifiers,
|
||||
touchesX,
|
||||
touchesY,
|
||||
radiisX,
|
||||
radiisY,
|
||||
rotationAngles,
|
||||
forces,
|
||||
count,
|
||||
modifiers);
|
||||
} else {
|
||||
this._sendAsyncMsg("send-touch-event", {
|
||||
"type": type,
|
||||
"identifiers": identifiers,
|
||||
"touchesX": touchesX,
|
||||
"touchesY": touchesY,
|
||||
"radiisX": radiisX,
|
||||
"radiisY": radiisY,
|
||||
"rotationAngles": rotationAngles,
|
||||
"forces": forces,
|
||||
"count": count,
|
||||
"modifiers": modifiers
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
getCanGoBack: defineDOMRequestMethod('get-can-go-back'),
|
||||
getCanGoForward: defineDOMRequestMethod('get-can-go-forward'),
|
||||
getContentDimensions: defineDOMRequestMethod('get-contentdimensions'),
|
||||
|
||||
goBack: defineNoReturnMethod(function() {
|
||||
this._sendAsyncMsg('go-back');
|
||||
}),
|
||||
|
||||
goForward: defineNoReturnMethod(function() {
|
||||
this._sendAsyncMsg('go-forward');
|
||||
}),
|
||||
|
||||
reload: defineNoReturnMethod(function(hardReload) {
|
||||
this._sendAsyncMsg('reload', {hardReload: hardReload});
|
||||
}),
|
||||
|
||||
stop: defineNoReturnMethod(function() {
|
||||
this._sendAsyncMsg('stop');
|
||||
}),
|
||||
|
||||
/*
|
||||
* The valid range of zoom scale is defined in preference "zoom.maxPercent" and "zoom.minPercent".
|
||||
*/
|
||||
zoom: defineNoReturnMethod(function(zoom) {
|
||||
zoom *= 100;
|
||||
zoom = Math.min(getIntPref("zoom.maxPercent", 300), zoom);
|
||||
zoom = Math.max(getIntPref("zoom.minPercent", 50), zoom);
|
||||
this._sendAsyncMsg('zoom', {zoom: zoom / 100.0});
|
||||
}),
|
||||
|
||||
purgeHistory: defineDOMRequestMethod('purge-history'),
|
||||
|
||||
|
||||
download: function(_url, _options) {
|
||||
if (!this._isAlive()) {
|
||||
return null;
|
||||
}
|
||||
let ioService =
|
||||
Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
|
||||
let uri = ioService.newURI(_url, null, null);
|
||||
let url = uri.QueryInterface(Ci.nsIURL);
|
||||
|
||||
// Ensure we have _options, we always use it to send the filename.
|
||||
_options = _options || {};
|
||||
if (!_options.filename) {
|
||||
_options.filename = url.fileName;
|
||||
}
|
||||
|
||||
debug('_options = ' + uneval(_options));
|
||||
|
||||
// Ensure we have a filename.
|
||||
if (!_options.filename) {
|
||||
throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
let interfaceRequestor =
|
||||
this._frameLoader.loadContext.QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
let req = Services.DOMRequest.createRequest(this._window);
|
||||
|
||||
function DownloadListener() {
|
||||
debug('DownloadListener Constructor');
|
||||
}
|
||||
DownloadListener.prototype = {
|
||||
extListener: null,
|
||||
onStartRequest: function(aRequest, aContext) {
|
||||
debug('DownloadListener - onStartRequest');
|
||||
let extHelperAppSvc =
|
||||
Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
|
||||
getService(Ci.nsIExternalHelperAppService);
|
||||
let channel = aRequest.QueryInterface(Ci.nsIChannel);
|
||||
|
||||
// First, we'll ensure the filename doesn't have any leading
|
||||
// periods. We have to do it here to avoid ending up with a filename
|
||||
// that's only an extension with no extension (e.g. Sending in
|
||||
// '.jpeg' without stripping the '.' would result in a filename of
|
||||
// 'jpeg' where we want 'jpeg.jpeg'.
|
||||
_options.filename = _options.filename.replace(/^\.+/, "");
|
||||
|
||||
let ext = null;
|
||||
let mimeSvc = extHelperAppSvc.QueryInterface(Ci.nsIMIMEService);
|
||||
try {
|
||||
ext = '.' + mimeSvc.getPrimaryExtension(channel.contentType, '');
|
||||
} catch (e) { ext = null; }
|
||||
|
||||
// Check if we need to add an extension to the filename.
|
||||
if (ext && !_options.filename.endsWith(ext)) {
|
||||
_options.filename += ext;
|
||||
}
|
||||
// Set the filename to use when saving to disk.
|
||||
channel.contentDispositionFilename = _options.filename;
|
||||
|
||||
this.extListener =
|
||||
extHelperAppSvc.doContent(
|
||||
channel.contentType,
|
||||
aRequest,
|
||||
interfaceRequestor,
|
||||
true);
|
||||
this.extListener.onStartRequest(aRequest, aContext);
|
||||
},
|
||||
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
||||
debug('DownloadListener - onStopRequest (aStatusCode = ' +
|
||||
aStatusCode + ')');
|
||||
if (aStatusCode == Cr.NS_OK) {
|
||||
// Everything looks great.
|
||||
debug('DownloadListener - Download Successful.');
|
||||
Services.DOMRequest.fireSuccess(req, aStatusCode);
|
||||
}
|
||||
else {
|
||||
// In case of failure, we'll simply return the failure status code.
|
||||
debug('DownloadListener - Download Failed!');
|
||||
Services.DOMRequest.fireError(req, aStatusCode);
|
||||
}
|
||||
|
||||
if (this.extListener) {
|
||||
this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
|
||||
}
|
||||
},
|
||||
onDataAvailable: function(aRequest, aContext, aInputStream,
|
||||
aOffset, aCount) {
|
||||
this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
|
||||
aOffset, aCount);
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener,
|
||||
Ci.nsIRequestObserver])
|
||||
};
|
||||
|
||||
let channel = ioService.newChannelFromURI(url);
|
||||
|
||||
// XXX We would set private browsing information prior to calling this.
|
||||
channel.notificationCallbacks = interfaceRequestor;
|
||||
|
||||
// Since we're downloading our own local copy we'll want to bypass the
|
||||
// cache and local cache if the channel let's us specify this.
|
||||
let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS |
|
||||
Ci.nsIChannel.LOAD_BYPASS_CACHE;
|
||||
if (channel instanceof Ci.nsICachingChannel) {
|
||||
debug('This is a caching channel. Forcing bypass.');
|
||||
flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
|
||||
}
|
||||
|
||||
channel.loadFlags |= flags;
|
||||
|
||||
if (channel instanceof Ci.nsIHttpChannel) {
|
||||
debug('Setting HTTP referrer = ' + this._window.document.documentURIObject);
|
||||
channel.referrer = this._window.document.documentURIObject;
|
||||
if (channel instanceof Ci.nsIHttpChannelInternal) {
|
||||
channel.forceAllowThirdPartyCookie = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set-up complete, let's get things started.
|
||||
channel.asyncOpen(new DownloadListener(), null);
|
||||
|
||||
return req;
|
||||
},
|
||||
|
||||
getScreenshot: function(_width, _height, _mimeType) {
|
||||
if (!this._isAlive()) {
|
||||
throw Components.Exception("Dead content process",
|
||||
Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
|
||||
let width = parseInt(_width);
|
||||
let height = parseInt(_height);
|
||||
let mimeType = (typeof _mimeType === 'string') ?
|
||||
_mimeType.trim().toLowerCase() : 'image/jpeg';
|
||||
if (isNaN(width) || isNaN(height) || width < 0 || height < 0) {
|
||||
throw Components.Exception("Invalid argument",
|
||||
Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
return this._sendDOMRequest('get-screenshot',
|
||||
{width: width, height: height,
|
||||
mimeType: mimeType});
|
||||
},
|
||||
|
||||
_recvNextPaint: function(data) {
|
||||
let listeners = this._nextPaintListeners;
|
||||
this._nextPaintListeners = [];
|
||||
for (let listener of listeners) {
|
||||
try {
|
||||
listener.recvNextPaint();
|
||||
} catch (e) {
|
||||
// If a listener throws we'll continue.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addNextPaintListener: function(listener) {
|
||||
if (!this._isAlive()) {
|
||||
throw Components.Exception("Dead content process",
|
||||
Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
|
||||
let self = this;
|
||||
let run = function() {
|
||||
if (self._nextPaintListeners.push(listener) == 1)
|
||||
self._sendAsyncMsg('activate-next-paint-listener');
|
||||
};
|
||||
if (!this._domRequestReady) {
|
||||
this._pendingAPICalls.push(run);
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
},
|
||||
|
||||
removeNextPaintListener: function(listener) {
|
||||
if (!this._isAlive()) {
|
||||
throw Components.Exception("Dead content process",
|
||||
Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
|
||||
let self = this;
|
||||
let run = function() {
|
||||
for (let i = self._nextPaintListeners.length - 1; i >= 0; i--) {
|
||||
if (self._nextPaintListeners[i] == listener) {
|
||||
self._nextPaintListeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (self._nextPaintListeners.length == 0)
|
||||
self._sendAsyncMsg('deactivate-next-paint-listener');
|
||||
};
|
||||
if (!this._domRequestReady) {
|
||||
this._pendingAPICalls.push(run);
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
},
|
||||
|
||||
setInputMethodActive: function(isActive) {
|
||||
if (!this._isAlive()) {
|
||||
throw Components.Exception("Dead content process",
|
||||
Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
|
||||
if (typeof isActive !== 'boolean') {
|
||||
throw Components.Exception("Invalid argument",
|
||||
Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
return this._sendDOMRequest('set-input-method-active',
|
||||
{isActive: isActive});
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the visibility of the window which owns this iframe changes.
|
||||
*/
|
||||
_ownerVisibilityChange: function() {
|
||||
this._sendAsyncMsg('owner-visibility-change',
|
||||
{visible: !this._window.document.hidden});
|
||||
},
|
||||
|
||||
/*
|
||||
* Called when the child notices that its visibility has changed.
|
||||
*
|
||||
* This is sometimes redundant; for example, the child's visibility may
|
||||
* change in response to a setVisible request that we made here! But it's
|
||||
* not always redundant; for example, the child's visibility may change in
|
||||
* response to its parent docshell being hidden.
|
||||
*/
|
||||
_childVisibilityChange: function(data) {
|
||||
debug("_childVisibilityChange(" + data.json.visible + ")");
|
||||
this._frameLoader.visible = data.json.visible;
|
||||
|
||||
this._fireEventFromMsg(data);
|
||||
},
|
||||
|
||||
_exitFullscreen: function() {
|
||||
this._windowUtils.exitFullscreen();
|
||||
},
|
||||
|
||||
_remoteFullscreenOriginChange: function(data) {
|
||||
let origin = data.json._payload_;
|
||||
this._windowUtils.remoteFrameFullscreenChanged(this._frameElement, origin);
|
||||
},
|
||||
|
||||
_remoteFrameFullscreenReverted: function(data) {
|
||||
this._windowUtils.remoteFrameFullscreenReverted();
|
||||
},
|
||||
|
||||
_fireFatalError: function() {
|
||||
let evt = this._createEvent('error', {type: 'fatal'},
|
||||
/* cancelable = */ false);
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
_createBrowserElementParent: function(frameLoader, hasRemoteFrame, isPendingFrame) {
|
||||
let frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
|
||||
this._bepMap.set(frameElement, BrowserElementParentBuilder.create(
|
||||
frameLoader, hasRemoteFrame, isPendingFrame));
|
||||
},
|
||||
|
||||
observe: function(subject, topic, data) {
|
||||
switch(topic) {
|
||||
case 'oop-frameloader-crashed':
|
||||
if (this._isAlive() && subject == this._frameLoader) {
|
||||
this._fireFatalError();
|
||||
case 'app-startup':
|
||||
this._init();
|
||||
break;
|
||||
case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
|
||||
if (data == BROWSER_FRAMES_ENABLED_PREF) {
|
||||
this._init();
|
||||
}
|
||||
break;
|
||||
case 'ask-children-to-exit-fullscreen':
|
||||
if (this._isAlive() &&
|
||||
this._frameElement.ownerDocument == subject &&
|
||||
this._frameLoader.QueryInterface(Ci.nsIFrameLoader).tabParent) {
|
||||
this._sendAsyncMsg('exit-fullscreen');
|
||||
}
|
||||
case 'remote-browser-pending':
|
||||
this._observeRemoteBrowserFramePending(subject);
|
||||
break;
|
||||
case 'copypaste-docommand':
|
||||
if (this._isAlive() && this._frameElement.isEqualNode(subject.wrappedJSObject)) {
|
||||
this._sendAsyncMsg('do-command', { command: data });
|
||||
}
|
||||
case 'inprocess-browser-shown':
|
||||
this._observeInProcessBrowserFrameShown(subject);
|
||||
break;
|
||||
default:
|
||||
debug('Unknown topic: ' + topic);
|
||||
break;
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParent]);
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParentFactory]);
|
||||
|
|
|
@ -0,0 +1,947 @@
|
|||
/* 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";
|
||||
|
||||
let Cu = Components.utils;
|
||||
let Ci = Components.interfaces;
|
||||
let Cc = Components.classes;
|
||||
let Cr = Components.results;
|
||||
|
||||
/* BrowserElementParent injects script to listen for certain events in the
|
||||
* child. We then listen to messages from the child script and take
|
||||
* appropriate action here in the parent.
|
||||
*/
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["BrowserElementParentBuilder"];
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function () {
|
||||
Cu.import("resource://gre/modules/Webapps.jsm");
|
||||
return DOMApplicationRegistry;
|
||||
});
|
||||
|
||||
const TOUCH_EVENTS_ENABLED_PREF = "dom.w3c_touch_events.enabled";
|
||||
|
||||
function debug(msg) {
|
||||
//dump("BrowserElementParent.jsm - " + msg + "\n");
|
||||
}
|
||||
|
||||
function getIntPref(prefName, def) {
|
||||
try {
|
||||
return Services.prefs.getIntPref(prefName);
|
||||
}
|
||||
catch(err) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
function visibilityChangeHandler(e) {
|
||||
// The visibilitychange event's target is the document.
|
||||
let win = e.target.defaultView;
|
||||
|
||||
if (!win._browserElementParents) {
|
||||
return;
|
||||
}
|
||||
|
||||
let beps = Cu.nondeterministicGetWeakMapKeys(win._browserElementParents);
|
||||
if (beps.length == 0) {
|
||||
win.removeEventListener('visibilitychange', visibilityChangeHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < beps.length; i++) {
|
||||
beps[i]._ownerVisibilityChange();
|
||||
}
|
||||
}
|
||||
|
||||
this.BrowserElementParentBuilder = {
|
||||
create: function create(frameLoader, hasRemoteFrame, isPendingFrame) {
|
||||
return new BrowserElementParent(frameLoader, hasRemoteFrame);
|
||||
}
|
||||
}
|
||||
|
||||
function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
|
||||
debug("Creating new BrowserElementParent object for " + frameLoader);
|
||||
this._domRequestCounter = 0;
|
||||
this._domRequestReady = false;
|
||||
this._pendingAPICalls = [];
|
||||
this._pendingDOMRequests = {};
|
||||
this._pendingSetInputMethodActive = [];
|
||||
this._hasRemoteFrame = hasRemoteFrame;
|
||||
this._nextPaintListeners = [];
|
||||
|
||||
this._frameLoader = frameLoader;
|
||||
this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
|
||||
let self = this;
|
||||
if (!this._frameElement) {
|
||||
debug("No frame element?");
|
||||
return;
|
||||
}
|
||||
|
||||
Services.obs.addObserver(this, 'ask-children-to-exit-fullscreen', /* ownsWeak = */ true);
|
||||
Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
|
||||
Services.obs.addObserver(this, 'copypaste-docommand', /* ownsWeak = */ true);
|
||||
|
||||
let defineMethod = function(name, fn) {
|
||||
XPCNativeWrapper.unwrap(self._frameElement)[name] = Cu.exportFunction(function() {
|
||||
if (self._isAlive()) {
|
||||
return fn.apply(self, arguments);
|
||||
}
|
||||
}, self._frameElement);
|
||||
}
|
||||
|
||||
let defineNoReturnMethod = function(name, fn) {
|
||||
XPCNativeWrapper.unwrap(self._frameElement)[name] = Cu.exportFunction(function method() {
|
||||
if (!self._domRequestReady) {
|
||||
// Remote browser haven't been created, we just queue the API call.
|
||||
let args = Array.slice(arguments);
|
||||
args.unshift(self);
|
||||
self._pendingAPICalls.push(method.bind.apply(fn, args));
|
||||
return;
|
||||
}
|
||||
if (self._isAlive()) {
|
||||
fn.apply(self, arguments);
|
||||
}
|
||||
}, self._frameElement);
|
||||
};
|
||||
|
||||
let defineDOMRequestMethod = function(domName, msgName) {
|
||||
XPCNativeWrapper.unwrap(self._frameElement)[domName] = Cu.exportFunction(function() {
|
||||
return self._sendDOMRequest(msgName);
|
||||
}, self._frameElement);
|
||||
}
|
||||
|
||||
// Define methods on the frame element.
|
||||
defineNoReturnMethod('setVisible', this._setVisible);
|
||||
defineDOMRequestMethod('getVisible', 'get-visible');
|
||||
|
||||
// Not expose security sensitive browser API for widgets
|
||||
if (!this._frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsWidget) {
|
||||
defineNoReturnMethod('sendMouseEvent', this._sendMouseEvent);
|
||||
|
||||
// 0 = disabled, 1 = enabled, 2 - auto detect
|
||||
if (getIntPref(TOUCH_EVENTS_ENABLED_PREF, 0) != 0) {
|
||||
defineNoReturnMethod('sendTouchEvent', this._sendTouchEvent);
|
||||
}
|
||||
defineNoReturnMethod('goBack', this._goBack);
|
||||
defineNoReturnMethod('goForward', this._goForward);
|
||||
defineNoReturnMethod('reload', this._reload);
|
||||
defineNoReturnMethod('stop', this._stop);
|
||||
defineMethod('download', this._download);
|
||||
defineDOMRequestMethod('purgeHistory', 'purge-history');
|
||||
defineMethod('getScreenshot', this._getScreenshot);
|
||||
defineNoReturnMethod('zoom', this._zoom);
|
||||
|
||||
defineDOMRequestMethod('getCanGoBack', 'get-can-go-back');
|
||||
defineDOMRequestMethod('getCanGoForward', 'get-can-go-forward');
|
||||
defineDOMRequestMethod('getContentDimensions', 'get-contentdimensions');
|
||||
}
|
||||
|
||||
defineMethod('addNextPaintListener', this._addNextPaintListener);
|
||||
defineMethod('removeNextPaintListener', this._removeNextPaintListener);
|
||||
defineNoReturnMethod('setActive', this._setActive);
|
||||
defineMethod('getActive', 'this._getActive');
|
||||
|
||||
let principal = this._frameElement.ownerDocument.nodePrincipal;
|
||||
let perm = Services.perms
|
||||
.testExactPermissionFromPrincipal(principal, "input-manage");
|
||||
if (perm === Ci.nsIPermissionManager.ALLOW_ACTION) {
|
||||
defineMethod('setInputMethodActive', this._setInputMethodActive);
|
||||
}
|
||||
|
||||
// Listen to visibilitychange on the iframe's owner window, and forward
|
||||
// changes down to the child. We want to do this while registering as few
|
||||
// visibilitychange listeners on _window as possible, because such a listener
|
||||
// may live longer than this BrowserElementParent object.
|
||||
//
|
||||
// To accomplish this, we register just one listener on the window, and have
|
||||
// it reference a WeakMap whose keys are all the BrowserElementParent objects
|
||||
// on the window. Then when the listener fires, we iterate over the
|
||||
// WeakMap's keys (which we can do, because we're chrome) to notify the
|
||||
// BrowserElementParents.
|
||||
if (!this._window._browserElementParents) {
|
||||
this._window._browserElementParents = new WeakMap();
|
||||
this._window.addEventListener('visibilitychange',
|
||||
visibilityChangeHandler,
|
||||
/* useCapture = */ false,
|
||||
/* wantsUntrusted = */ false);
|
||||
}
|
||||
|
||||
this._window._browserElementParents.set(this, null);
|
||||
|
||||
// Insert ourself into the prompt service.
|
||||
BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
|
||||
if (!isPendingFrame) {
|
||||
this._setupMessageListener();
|
||||
this._registerAppManifest();
|
||||
} else {
|
||||
// if we are a pending frame, we setup message manager after
|
||||
// observing remote-browser-frame-shown
|
||||
Services.obs.addObserver(this, 'remote-browser-frame-shown', /* ownsWeak = */ true);
|
||||
}
|
||||
}
|
||||
|
||||
BrowserElementParent.prototype = {
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
_runPendingAPICall: function() {
|
||||
if (!this._pendingAPICalls) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < this._pendingAPICalls.length; i++) {
|
||||
try {
|
||||
this._pendingAPICalls[i]();
|
||||
} catch (e) {
|
||||
// throw the expections from pending functions.
|
||||
debug('Exception when running pending API call: ' + e);
|
||||
}
|
||||
}
|
||||
delete this._pendingAPICalls;
|
||||
},
|
||||
|
||||
_registerAppManifest: function() {
|
||||
// If this browser represents an app then let the Webapps module register for
|
||||
// any messages that it needs.
|
||||
let appManifestURL =
|
||||
this._frameElement.QueryInterface(Ci.nsIMozBrowserFrame).appManifestURL;
|
||||
if (appManifestURL) {
|
||||
let inParent = Cc["@mozilla.org/xre/app-info;1"]
|
||||
.getService(Ci.nsIXULRuntime)
|
||||
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
|
||||
if (inParent) {
|
||||
DOMApplicationRegistry.registerBrowserElementParentForApp(
|
||||
{ manifestURL: appManifestURL }, this._mm);
|
||||
} else {
|
||||
this._mm.sendAsyncMessage("Webapps:RegisterBEP",
|
||||
{ manifestURL: appManifestURL });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_setupMessageListener: function() {
|
||||
this._mm = this._frameLoader.messageManager;
|
||||
let self = this;
|
||||
let isWidget = this._frameLoader
|
||||
.QueryInterface(Ci.nsIFrameLoader)
|
||||
.ownerIsWidget;
|
||||
|
||||
// Messages we receive are handed to functions which take a (data) argument,
|
||||
// where |data| is the message manager's data object.
|
||||
// We use a single message and dispatch to various function based
|
||||
// on data.msg_name
|
||||
let mmCalls = {
|
||||
"hello": this._recvHello,
|
||||
"loadstart": this._fireProfiledEventFromMsg,
|
||||
"loadend": this._fireProfiledEventFromMsg,
|
||||
"close": this._fireEventFromMsg,
|
||||
"error": this._fireEventFromMsg,
|
||||
"firstpaint": this._fireProfiledEventFromMsg,
|
||||
"documentfirstpaint": this._fireProfiledEventFromMsg,
|
||||
"nextpaint": this._recvNextPaint,
|
||||
"got-purge-history": this._gotDOMRequestResult,
|
||||
"got-screenshot": this._gotDOMRequestResult,
|
||||
"got-contentdimensions": this._gotDOMRequestResult,
|
||||
"got-can-go-back": this._gotDOMRequestResult,
|
||||
"got-can-go-forward": this._gotDOMRequestResult,
|
||||
"fullscreen-origin-change": this._remoteFullscreenOriginChange,
|
||||
"rollback-fullscreen": this._remoteFrameFullscreenReverted,
|
||||
"exit-fullscreen": this._exitFullscreen,
|
||||
"got-visible": this._gotDOMRequestResult,
|
||||
"visibilitychange": this._childVisibilityChange,
|
||||
"got-set-input-method-active": this._gotDOMRequestResult,
|
||||
"selectionchange": this._handleSelectionChange,
|
||||
"scrollviewchange": this._handleScrollViewChange,
|
||||
"touchcarettap": this._handleTouchCaretTap
|
||||
};
|
||||
|
||||
let mmSecuritySensitiveCalls = {
|
||||
"showmodalprompt": this._handleShowModalPrompt,
|
||||
"contextmenu": this._fireCtxMenuEvent,
|
||||
"securitychange": this._fireEventFromMsg,
|
||||
"locationchange": this._fireEventFromMsg,
|
||||
"iconchange": this._fireEventFromMsg,
|
||||
"scrollareachanged": this._fireEventFromMsg,
|
||||
"titlechange": this._fireProfiledEventFromMsg,
|
||||
"opensearch": this._fireEventFromMsg,
|
||||
"manifestchange": this._fireEventFromMsg,
|
||||
"metachange": this._fireEventFromMsg,
|
||||
"resize": this._fireEventFromMsg,
|
||||
"activitydone": this._fireEventFromMsg,
|
||||
"scroll": this._fireEventFromMsg
|
||||
};
|
||||
|
||||
this._mm.addMessageListener('browser-element-api:call', function(aMsg) {
|
||||
if (!self._isAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aMsg.data.msg_name in mmCalls) {
|
||||
return mmCalls[aMsg.data.msg_name].apply(self, arguments);
|
||||
} else if (!isWidget && aMsg.data.msg_name in mmSecuritySensitiveCalls) {
|
||||
return mmSecuritySensitiveCalls[aMsg.data.msg_name]
|
||||
.apply(self, arguments);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* You shouldn't touch this._frameElement or this._window if _isAlive is
|
||||
* false. (You'll likely get an exception if you do.)
|
||||
*/
|
||||
_isAlive: function() {
|
||||
return !Cu.isDeadWrapper(this._frameElement) &&
|
||||
!Cu.isDeadWrapper(this._frameElement.ownerDocument) &&
|
||||
!Cu.isDeadWrapper(this._frameElement.ownerDocument.defaultView);
|
||||
},
|
||||
|
||||
get _window() {
|
||||
return this._frameElement.ownerDocument.defaultView;
|
||||
},
|
||||
|
||||
get _windowUtils() {
|
||||
return this._window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
},
|
||||
|
||||
promptAuth: function(authDetail, callback) {
|
||||
let evt;
|
||||
let self = this;
|
||||
let callbackCalled = false;
|
||||
let cancelCallback = function() {
|
||||
if (!callbackCalled) {
|
||||
callbackCalled = true;
|
||||
callback(false, null, null);
|
||||
}
|
||||
};
|
||||
|
||||
// 1. We don't handle password-only prompts.
|
||||
// 2. We don't handle for widget case because of security concern.
|
||||
if (authDetail.isOnlyPassword ||
|
||||
this._frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsWidget) {
|
||||
cancelCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
/* username and password */
|
||||
let detail = {
|
||||
host: authDetail.host,
|
||||
realm: authDetail.realm
|
||||
};
|
||||
|
||||
evt = this._createEvent('usernameandpasswordrequired', detail,
|
||||
/* cancelable */ true);
|
||||
Cu.exportFunction(function(username, password) {
|
||||
if (callbackCalled)
|
||||
return;
|
||||
callbackCalled = true;
|
||||
callback(true, username, password);
|
||||
}, evt.detail, { defineAs: 'authenticate' });
|
||||
|
||||
Cu.exportFunction(cancelCallback, evt.detail, { defineAs: 'cancel' });
|
||||
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
|
||||
if (!evt.defaultPrevented) {
|
||||
cancelCallback();
|
||||
}
|
||||
},
|
||||
|
||||
_sendAsyncMsg: function(msg, data) {
|
||||
try {
|
||||
if (!data) {
|
||||
data = { };
|
||||
}
|
||||
|
||||
data.msg_name = msg;
|
||||
this._mm.sendAsyncMessage('browser-element-api:call', data);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_recvHello: function() {
|
||||
debug("recvHello");
|
||||
|
||||
// Inform our child if our owner element's document is invisible. Note
|
||||
// that we must do so here, rather than in the BrowserElementParent
|
||||
// constructor, because the BrowserElementChild may not be initialized when
|
||||
// we run our constructor.
|
||||
if (this._window.document.hidden) {
|
||||
this._ownerVisibilityChange();
|
||||
}
|
||||
|
||||
if (!this._domRequestReady) {
|
||||
// At least, one message listener such as for hello is registered.
|
||||
// So we can use sendAsyncMessage now.
|
||||
this._domRequestReady = true;
|
||||
this._runPendingAPICall();
|
||||
}
|
||||
|
||||
return {
|
||||
name: this._frameElement.getAttribute('name'),
|
||||
fullscreenAllowed:
|
||||
this._frameElement.hasAttribute('allowfullscreen') ||
|
||||
this._frameElement.hasAttribute('mozallowfullscreen'),
|
||||
isPrivate: this._frameElement.hasAttribute('mozprivatebrowsing')
|
||||
};
|
||||
},
|
||||
|
||||
_fireCtxMenuEvent: function(data) {
|
||||
let detail = data.json;
|
||||
let evtName = detail.msg_name;
|
||||
|
||||
debug('fireCtxMenuEventFromMsg: ' + evtName + ' ' + detail);
|
||||
let evt = this._createEvent(evtName, detail, /* cancellable */ true);
|
||||
|
||||
if (detail.contextmenu) {
|
||||
var self = this;
|
||||
Cu.exportFunction(function(id) {
|
||||
self._sendAsyncMsg('fire-ctx-callback', {menuitem: id});
|
||||
}, evt.detail, { defineAs: 'contextMenuItemSelected' });
|
||||
}
|
||||
|
||||
// The embedder may have default actions on context menu events, so
|
||||
// we fire a context menu event even if the child didn't define a
|
||||
// custom context menu
|
||||
return !this._frameElement.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
/**
|
||||
* add profiler marker for each event fired.
|
||||
*/
|
||||
_fireProfiledEventFromMsg: function(data) {
|
||||
if (Services.profiler !== undefined) {
|
||||
Services.profiler.AddMarker(data.json.msg_name);
|
||||
}
|
||||
this._fireEventFromMsg(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fire either a vanilla or a custom event, depending on the contents of
|
||||
* |data|.
|
||||
*/
|
||||
_fireEventFromMsg: function(data) {
|
||||
let detail = data.json;
|
||||
let name = detail.msg_name;
|
||||
|
||||
// For events that send a "_payload_" property, we just want to transmit
|
||||
// this in the event.
|
||||
if ("_payload_" in detail) {
|
||||
detail = detail._payload_;
|
||||
}
|
||||
|
||||
debug('fireEventFromMsg: ' + name + ', ' + JSON.stringify(detail));
|
||||
let evt = this._createEvent(name, detail,
|
||||
/* cancelable = */ false);
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
_handleShowModalPrompt: function(data) {
|
||||
// Fire a showmodalprmopt event on the iframe. When this method is called,
|
||||
// the child is spinning in a nested event loop waiting for an
|
||||
// unblock-modal-prompt message.
|
||||
//
|
||||
// If the embedder calls preventDefault() on the showmodalprompt event,
|
||||
// we'll block the child until event.detail.unblock() is called.
|
||||
//
|
||||
// Otherwise, if preventDefault() is not called, we'll send the
|
||||
// unblock-modal-prompt message to the child as soon as the event is done
|
||||
// dispatching.
|
||||
|
||||
let detail = data.json;
|
||||
debug('handleShowPrompt ' + JSON.stringify(detail));
|
||||
|
||||
// Strip off the windowID property from the object we send along in the
|
||||
// event.
|
||||
let windowID = detail.windowID;
|
||||
delete detail.windowID;
|
||||
debug("Event will have detail: " + JSON.stringify(detail));
|
||||
let evt = this._createEvent('showmodalprompt', detail,
|
||||
/* cancelable = */ true);
|
||||
|
||||
let self = this;
|
||||
let unblockMsgSent = false;
|
||||
function sendUnblockMsg() {
|
||||
if (unblockMsgSent) {
|
||||
return;
|
||||
}
|
||||
unblockMsgSent = true;
|
||||
|
||||
// We don't need to sanitize evt.detail.returnValue (e.g. converting the
|
||||
// return value of confirm() to a boolean); Gecko does that for us.
|
||||
|
||||
let data = { windowID: windowID,
|
||||
returnValue: evt.detail.returnValue };
|
||||
self._sendAsyncMsg('unblock-modal-prompt', data);
|
||||
}
|
||||
|
||||
Cu.exportFunction(sendUnblockMsg, evt.detail, { defineAs: 'unblock' });
|
||||
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
|
||||
if (!evt.defaultPrevented) {
|
||||
// Unblock the inner frame immediately. Otherwise we'll unblock upon
|
||||
// evt.detail.unblock().
|
||||
sendUnblockMsg();
|
||||
}
|
||||
},
|
||||
|
||||
_handleSelectionChange: function(data) {
|
||||
let evt = this._createEvent('selectionchange', data.json,
|
||||
/* cancelable = */ false);
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
_handleScrollViewChange: function(data) {
|
||||
let evt = this._createEvent("scrollviewchange", data.json,
|
||||
/* cancelable = */ false);
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
_handleTouchCaretTap: function(data) {
|
||||
let evt = this._createEvent("touchcarettap", data.json,
|
||||
/* cancelable = */ false);
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
_createEvent: function(evtName, detail, cancelable) {
|
||||
// This will have to change if we ever want to send a CustomEvent with null
|
||||
// detail. For now, it's OK.
|
||||
if (detail !== undefined && detail !== null) {
|
||||
detail = Cu.cloneInto(detail, this._window);
|
||||
return new this._window.CustomEvent('mozbrowser' + evtName,
|
||||
{ bubbles: true,
|
||||
cancelable: cancelable,
|
||||
detail: detail });
|
||||
}
|
||||
|
||||
return new this._window.Event('mozbrowser' + evtName,
|
||||
{ bubbles: true,
|
||||
cancelable: cancelable });
|
||||
},
|
||||
|
||||
/**
|
||||
* Kick off a DOMRequest in the child process.
|
||||
*
|
||||
* We'll fire an event called |msgName| on the child process, passing along
|
||||
* an object with two fields:
|
||||
*
|
||||
* - id: the ID of this request.
|
||||
* - arg: arguments to pass to the child along with this request.
|
||||
*
|
||||
* We expect the child to pass the ID back to us upon completion of the
|
||||
* request. See _gotDOMRequestResult.
|
||||
*/
|
||||
_sendDOMRequest: function(msgName, args) {
|
||||
let id = 'req_' + this._domRequestCounter++;
|
||||
let req = Services.DOMRequest.createRequest(this._window);
|
||||
let self = this;
|
||||
let send = function() {
|
||||
if (!self._isAlive()) {
|
||||
return;
|
||||
}
|
||||
if (self._sendAsyncMsg(msgName, {id: id, args: args})) {
|
||||
self._pendingDOMRequests[id] = req;
|
||||
} else {
|
||||
Services.DOMRequest.fireErrorAsync(req, "fail");
|
||||
}
|
||||
};
|
||||
if (this._domRequestReady) {
|
||||
send();
|
||||
} else {
|
||||
// Child haven't been loaded.
|
||||
this._pendingAPICalls.push(send);
|
||||
}
|
||||
return req;
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the child process finishes handling a DOMRequest. data.json
|
||||
* must have the fields [id, successRv], if the DOMRequest was successful, or
|
||||
* [id, errorMsg], if the request was not successful.
|
||||
*
|
||||
* The fields have the following meanings:
|
||||
*
|
||||
* - id: the ID of the DOM request (see _sendDOMRequest)
|
||||
* - successRv: the request's return value, if the request succeeded
|
||||
* - errorMsg: the message to pass to DOMRequest.fireError(), if the request
|
||||
* failed.
|
||||
*
|
||||
*/
|
||||
_gotDOMRequestResult: function(data) {
|
||||
let req = this._pendingDOMRequests[data.json.id];
|
||||
delete this._pendingDOMRequests[data.json.id];
|
||||
|
||||
if ('successRv' in data.json) {
|
||||
debug("Successful gotDOMRequestResult.");
|
||||
let clientObj = Cu.cloneInto(data.json.successRv, this._window);
|
||||
Services.DOMRequest.fireSuccess(req, clientObj);
|
||||
}
|
||||
else {
|
||||
debug("Got error in gotDOMRequestResult.");
|
||||
Services.DOMRequest.fireErrorAsync(req,
|
||||
Cu.cloneInto(data.json.errorMsg, this._window));
|
||||
}
|
||||
},
|
||||
|
||||
_setVisible: function(visible) {
|
||||
this._sendAsyncMsg('set-visible', {visible: visible});
|
||||
this._frameLoader.visible = visible;
|
||||
},
|
||||
|
||||
_setActive: function(active) {
|
||||
this._frameLoader.visible = active;
|
||||
},
|
||||
|
||||
_getActive: function() {
|
||||
return this._frameLoader.visible;
|
||||
},
|
||||
|
||||
_sendMouseEvent: function(type, x, y, button, clickCount, modifiers) {
|
||||
this._sendAsyncMsg("send-mouse-event", {
|
||||
"type": type,
|
||||
"x": x,
|
||||
"y": y,
|
||||
"button": button,
|
||||
"clickCount": clickCount,
|
||||
"modifiers": modifiers
|
||||
});
|
||||
},
|
||||
|
||||
_sendTouchEvent: function(type, identifiers, touchesX, touchesY,
|
||||
radiisX, radiisY, rotationAngles, forces,
|
||||
count, modifiers) {
|
||||
|
||||
let tabParent = this._frameLoader.tabParent;
|
||||
if (tabParent && tabParent.useAsyncPanZoom) {
|
||||
tabParent.injectTouchEvent(type,
|
||||
identifiers,
|
||||
touchesX,
|
||||
touchesY,
|
||||
radiisX,
|
||||
radiisY,
|
||||
rotationAngles,
|
||||
forces,
|
||||
count,
|
||||
modifiers);
|
||||
} else {
|
||||
this._sendAsyncMsg("send-touch-event", {
|
||||
"type": type,
|
||||
"identifiers": identifiers,
|
||||
"touchesX": touchesX,
|
||||
"touchesY": touchesY,
|
||||
"radiisX": radiisX,
|
||||
"radiisY": radiisY,
|
||||
"rotationAngles": rotationAngles,
|
||||
"forces": forces,
|
||||
"count": count,
|
||||
"modifiers": modifiers
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_goBack: function() {
|
||||
this._sendAsyncMsg('go-back');
|
||||
},
|
||||
|
||||
_goForward: function() {
|
||||
this._sendAsyncMsg('go-forward');
|
||||
},
|
||||
|
||||
_reload: function(hardReload) {
|
||||
this._sendAsyncMsg('reload', {hardReload: hardReload});
|
||||
},
|
||||
|
||||
_stop: function() {
|
||||
this._sendAsyncMsg('stop');
|
||||
},
|
||||
|
||||
/*
|
||||
* The valid range of zoom scale is defined in preference "zoom.maxPercent" and "zoom.minPercent".
|
||||
*/
|
||||
_zoom: function(zoom) {
|
||||
zoom *= 100;
|
||||
zoom = Math.min(getIntPref("zoom.maxPercent", 300), zoom);
|
||||
zoom = Math.max(getIntPref("zoom.minPercent", 50), zoom);
|
||||
this._sendAsyncMsg('zoom', {zoom: zoom / 100.0});
|
||||
},
|
||||
|
||||
_download: function(_url, _options) {
|
||||
let ioService =
|
||||
Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
|
||||
let uri = ioService.newURI(_url, null, null);
|
||||
let url = uri.QueryInterface(Ci.nsIURL);
|
||||
|
||||
// Ensure we have _options, we always use it to send the filename.
|
||||
_options = _options || {};
|
||||
if (!_options.filename) {
|
||||
_options.filename = url.fileName;
|
||||
}
|
||||
|
||||
debug('_options = ' + uneval(_options));
|
||||
|
||||
// Ensure we have a filename.
|
||||
if (!_options.filename) {
|
||||
throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
let interfaceRequestor =
|
||||
this._frameLoader.loadContext.QueryInterface(Ci.nsIInterfaceRequestor);
|
||||
let req = Services.DOMRequest.createRequest(this._window);
|
||||
|
||||
function DownloadListener() {
|
||||
debug('DownloadListener Constructor');
|
||||
}
|
||||
DownloadListener.prototype = {
|
||||
extListener: null,
|
||||
onStartRequest: function(aRequest, aContext) {
|
||||
debug('DownloadListener - onStartRequest');
|
||||
let extHelperAppSvc =
|
||||
Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
|
||||
getService(Ci.nsIExternalHelperAppService);
|
||||
let channel = aRequest.QueryInterface(Ci.nsIChannel);
|
||||
|
||||
// First, we'll ensure the filename doesn't have any leading
|
||||
// periods. We have to do it here to avoid ending up with a filename
|
||||
// that's only an extension with no extension (e.g. Sending in
|
||||
// '.jpeg' without stripping the '.' would result in a filename of
|
||||
// 'jpeg' where we want 'jpeg.jpeg'.
|
||||
_options.filename = _options.filename.replace(/^\.+/, "");
|
||||
|
||||
let ext = null;
|
||||
let mimeSvc = extHelperAppSvc.QueryInterface(Ci.nsIMIMEService);
|
||||
try {
|
||||
ext = '.' + mimeSvc.getPrimaryExtension(channel.contentType, '');
|
||||
} catch (e) { ext = null; }
|
||||
|
||||
// Check if we need to add an extension to the filename.
|
||||
if (ext && !_options.filename.endsWith(ext)) {
|
||||
_options.filename += ext;
|
||||
}
|
||||
// Set the filename to use when saving to disk.
|
||||
channel.contentDispositionFilename = _options.filename;
|
||||
|
||||
this.extListener =
|
||||
extHelperAppSvc.doContent(
|
||||
channel.contentType,
|
||||
aRequest,
|
||||
interfaceRequestor,
|
||||
true);
|
||||
this.extListener.onStartRequest(aRequest, aContext);
|
||||
},
|
||||
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
||||
debug('DownloadListener - onStopRequest (aStatusCode = ' +
|
||||
aStatusCode + ')');
|
||||
if (aStatusCode == Cr.NS_OK) {
|
||||
// Everything looks great.
|
||||
debug('DownloadListener - Download Successful.');
|
||||
Services.DOMRequest.fireSuccess(req, aStatusCode);
|
||||
}
|
||||
else {
|
||||
// In case of failure, we'll simply return the failure status code.
|
||||
debug('DownloadListener - Download Failed!');
|
||||
Services.DOMRequest.fireError(req, aStatusCode);
|
||||
}
|
||||
|
||||
if (this.extListener) {
|
||||
this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
|
||||
}
|
||||
},
|
||||
onDataAvailable: function(aRequest, aContext, aInputStream,
|
||||
aOffset, aCount) {
|
||||
this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
|
||||
aOffset, aCount);
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener,
|
||||
Ci.nsIRequestObserver])
|
||||
};
|
||||
|
||||
let channel = ioService.newChannelFromURI(url);
|
||||
|
||||
// XXX We would set private browsing information prior to calling this.
|
||||
channel.notificationCallbacks = interfaceRequestor;
|
||||
|
||||
// Since we're downloading our own local copy we'll want to bypass the
|
||||
// cache and local cache if the channel let's us specify this.
|
||||
let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS |
|
||||
Ci.nsIChannel.LOAD_BYPASS_CACHE;
|
||||
if (channel instanceof Ci.nsICachingChannel) {
|
||||
debug('This is a caching channel. Forcing bypass.');
|
||||
flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
|
||||
}
|
||||
|
||||
channel.loadFlags |= flags;
|
||||
|
||||
if (channel instanceof Ci.nsIHttpChannel) {
|
||||
debug('Setting HTTP referrer = ' + this._window.document.documentURIObject);
|
||||
channel.referrer = this._window.document.documentURIObject;
|
||||
if (channel instanceof Ci.nsIHttpChannelInternal) {
|
||||
channel.forceAllowThirdPartyCookie = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set-up complete, let's get things started.
|
||||
channel.asyncOpen(new DownloadListener(), null);
|
||||
|
||||
return req;
|
||||
},
|
||||
|
||||
_getScreenshot: function(_width, _height, _mimeType) {
|
||||
let width = parseInt(_width);
|
||||
let height = parseInt(_height);
|
||||
let mimeType = (typeof _mimeType === 'string') ?
|
||||
_mimeType.trim().toLowerCase() : 'image/jpeg';
|
||||
if (isNaN(width) || isNaN(height) || width < 0 || height < 0) {
|
||||
throw Components.Exception("Invalid argument",
|
||||
Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
return this._sendDOMRequest('get-screenshot',
|
||||
{width: width, height: height,
|
||||
mimeType: mimeType});
|
||||
},
|
||||
|
||||
_recvNextPaint: function(data) {
|
||||
let listeners = this._nextPaintListeners;
|
||||
this._nextPaintListeners = [];
|
||||
for (let listener of listeners) {
|
||||
try {
|
||||
listener();
|
||||
} catch (e) {
|
||||
// If a listener throws we'll continue.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_addNextPaintListener: function(listener) {
|
||||
if (typeof listener != 'function')
|
||||
throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
|
||||
|
||||
let self = this;
|
||||
let run = function() {
|
||||
if (self._nextPaintListeners.push(listener) == 1)
|
||||
self._sendAsyncMsg('activate-next-paint-listener');
|
||||
};
|
||||
if (!this._domRequestReady) {
|
||||
this._pendingAPICalls.push(run);
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
},
|
||||
|
||||
_removeNextPaintListener: function(listener) {
|
||||
if (typeof listener != 'function')
|
||||
throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
|
||||
|
||||
let self = this;
|
||||
let run = function() {
|
||||
for (let i = self._nextPaintListeners.length - 1; i >= 0; i--) {
|
||||
if (self._nextPaintListeners[i] == listener) {
|
||||
self._nextPaintListeners.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (self._nextPaintListeners.length == 0)
|
||||
self._sendAsyncMsg('deactivate-next-paint-listener');
|
||||
};
|
||||
if (!this._domRequestReady) {
|
||||
this._pendingAPICalls.push(run);
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
},
|
||||
|
||||
_setInputMethodActive: function(isActive) {
|
||||
if (typeof isActive !== 'boolean') {
|
||||
throw Components.Exception("Invalid argument",
|
||||
Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
return this._sendDOMRequest('set-input-method-active',
|
||||
{isActive: isActive});
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the visibility of the window which owns this iframe changes.
|
||||
*/
|
||||
_ownerVisibilityChange: function() {
|
||||
this._sendAsyncMsg('owner-visibility-change',
|
||||
{visible: !this._window.document.hidden});
|
||||
},
|
||||
|
||||
/*
|
||||
* Called when the child notices that its visibility has changed.
|
||||
*
|
||||
* This is sometimes redundant; for example, the child's visibility may
|
||||
* change in response to a setVisible request that we made here! But it's
|
||||
* not always redundant; for example, the child's visibility may change in
|
||||
* response to its parent docshell being hidden.
|
||||
*/
|
||||
_childVisibilityChange: function(data) {
|
||||
debug("_childVisibilityChange(" + data.json.visible + ")");
|
||||
this._frameLoader.visible = data.json.visible;
|
||||
|
||||
this._fireEventFromMsg(data);
|
||||
},
|
||||
|
||||
_exitFullscreen: function() {
|
||||
this._windowUtils.exitFullscreen();
|
||||
},
|
||||
|
||||
_remoteFullscreenOriginChange: function(data) {
|
||||
let origin = data.json._payload_;
|
||||
this._windowUtils.remoteFrameFullscreenChanged(this._frameElement, origin);
|
||||
},
|
||||
|
||||
_remoteFrameFullscreenReverted: function(data) {
|
||||
this._windowUtils.remoteFrameFullscreenReverted();
|
||||
},
|
||||
|
||||
_fireFatalError: function() {
|
||||
let evt = this._createEvent('error', {type: 'fatal'},
|
||||
/* cancelable = */ false);
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
observe: function(subject, topic, data) {
|
||||
switch(topic) {
|
||||
case 'oop-frameloader-crashed':
|
||||
if (this._isAlive() && subject == this._frameLoader) {
|
||||
this._fireFatalError();
|
||||
}
|
||||
break;
|
||||
case 'ask-children-to-exit-fullscreen':
|
||||
if (this._isAlive() &&
|
||||
this._frameElement.ownerDocument == subject &&
|
||||
this._hasRemoteFrame) {
|
||||
this._sendAsyncMsg('exit-fullscreen');
|
||||
}
|
||||
break;
|
||||
case 'remote-browser-frame-shown':
|
||||
if (this._frameLoader == subject) {
|
||||
if (!this._mm) {
|
||||
this._setupMessageListener();
|
||||
this._registerAppManifest();
|
||||
}
|
||||
Services.obs.removeObserver(this, 'remote-browser-frame-shown');
|
||||
}
|
||||
case 'copypaste-docommand':
|
||||
if (this._isAlive() && this._frameElement.isEqualNode(subject.wrappedJSObject)) {
|
||||
this._sendAsyncMsg('do-command', { command: data });
|
||||
}
|
||||
break;
|
||||
default:
|
||||
debug('Unknown topic: ' + topic);
|
||||
break;
|
||||
};
|
||||
},
|
||||
};
|
|
@ -1,2 +1,3 @@
|
|||
component {9f171ac4-0939-4ef8-b360-3408aedc3060} BrowserElementParent.js
|
||||
contract @mozilla.org/dom/browser-element-api;1 {9f171ac4-0939-4ef8-b360-3408aedc3060}
|
||||
component {ddeafdac-cb39-47c4-9cb8-c9027ee36d26} BrowserElementParent.js
|
||||
contract @mozilla.org/browser-element-parent-factory;1 {ddeafdac-cb39-47c4-9cb8-c9027ee36d26}
|
||||
category app-startup BrowserElementParentFactory service,@mozilla.org/browser-element-parent-factory;1
|
||||
|
|
|
@ -12,18 +12,13 @@ SOURCES += [
|
|||
'BrowserElementParent.cpp',
|
||||
]
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsIBrowserElementAPI.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'browser-element'
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'BrowserElementParent.js',
|
||||
'BrowserElementParent.manifest',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'BrowserElementParent.jsm',
|
||||
'BrowserElementPromptService.jsm',
|
||||
]
|
||||
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIDOMDOMRequest;
|
||||
interface nsIFrameLoader;
|
||||
|
||||
[scriptable, function, uuid(c0c2dd9b-41ef-42dd-a4c1-e456619c1941)]
|
||||
interface nsIBrowserElementNextPaintListener : nsISupports
|
||||
{
|
||||
void recvNextPaint();
|
||||
};
|
||||
|
||||
%{C++
|
||||
#define BROWSER_ELEMENT_API_CONTRACTID "@mozilla.org/dom/browser-element-api;1"
|
||||
#define BROWSER_ELEMENT_API_CID \
|
||||
{ 0x651db7e3, 0x1734, 0x4536, \
|
||||
{ 0xb1, 0x5a, 0x5b, 0x3a, 0xe6, 0x44, 0x13, 0x4c } }
|
||||
%}
|
||||
|
||||
/**
|
||||
* Interface to the BrowserElementParent implementation. All methods
|
||||
* but setFrameLoader throw when the remote process is dead.
|
||||
*/
|
||||
[scriptable, uuid(abae4fb1-7d6f-4e3f-b435-6501f1d4c659)]
|
||||
interface nsIBrowserElementAPI : nsISupports
|
||||
{
|
||||
void setFrameLoader(in nsIFrameLoader frameLoader);
|
||||
|
||||
void setVisible(in boolean visible);
|
||||
nsIDOMDOMRequest getVisible();
|
||||
void setActive(in boolean active);
|
||||
boolean getActive();
|
||||
|
||||
void sendMouseEvent(in DOMString type,
|
||||
in uint32_t x,
|
||||
in uint32_t y,
|
||||
in uint32_t button,
|
||||
in uint32_t clickCount,
|
||||
in uint32_t mifiers);
|
||||
void sendTouchEvent(in DOMString aType,
|
||||
[const, array, size_is(count)] in uint32_t aIdentifiers,
|
||||
[const, array, size_is(count)] in int32_t aXs,
|
||||
[const, array, size_is(count)] in int32_t aYs,
|
||||
[const, array, size_is(count)] in uint32_t aRxs,
|
||||
[const, array, size_is(count)] in uint32_t aRys,
|
||||
[const, array, size_is(count)] in float aRotationAngles,
|
||||
[const, array, size_is(count)] in float aForces,
|
||||
in uint32_t count,
|
||||
in long aModifiers);
|
||||
void goBack();
|
||||
void goForward();
|
||||
void reload(in boolean hardReload);
|
||||
void stop();
|
||||
nsIDOMDOMRequest download(in DOMString url,
|
||||
[optional] in jsval options);
|
||||
nsIDOMDOMRequest purgeHistory();
|
||||
nsIDOMDOMRequest getScreenshot(in uint32_t width,
|
||||
in uint32_t height,
|
||||
[optional] in DOMString mimeType);
|
||||
void zoom(in float zoom);
|
||||
nsIDOMDOMRequest getCanGoBack();
|
||||
nsIDOMDOMRequest getCanGoForward();
|
||||
nsIDOMDOMRequest getContentDimensions();
|
||||
|
||||
void addNextPaintListener(in nsIBrowserElementNextPaintListener listener);
|
||||
void removeNextPaintListener(in nsIBrowserElementNextPaintListener listener);
|
||||
|
||||
nsIDOMDOMRequest setInputMethodActive(in boolean isActive);
|
||||
};
|
|
@ -112,7 +112,6 @@ EXPORTS.mozilla.dom += [
|
|||
'HTMLVideoElement.h',
|
||||
'ImageDocument.h',
|
||||
'MediaError.h',
|
||||
'nsBrowserElement.h',
|
||||
'RadioNodeList.h',
|
||||
'TextTrackManager.h',
|
||||
'TimeRanges.h',
|
||||
|
@ -191,7 +190,6 @@ UNIFIED_SOURCES += [
|
|||
'ImageDocument.cpp',
|
||||
'MediaDocument.cpp',
|
||||
'MediaError.cpp',
|
||||
'nsBrowserElement.cpp',
|
||||
'nsDOMStringMap.cpp',
|
||||
'nsFormSubmission.cpp',
|
||||
'nsGenericHTMLElement.cpp',
|
||||
|
|
|
@ -1,545 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsBrowserElement.h"
|
||||
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/dom/BrowserElementBinding.h"
|
||||
#include "mozilla/dom/DOMRequest.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/dom/ToJSValue.h"
|
||||
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsFrameLoader.h"
|
||||
#include "nsIDOMDOMRequest.h"
|
||||
#include "nsIDOMElement.h"
|
||||
#include "nsINode.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIPrincipal.h"
|
||||
#include "nsWeakReference.h"
|
||||
|
||||
using namespace mozilla::dom;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
static const char kRemoteBrowserPending[] = "remote-browser-pending";
|
||||
static const char kInprocessBrowserShown[] = "inprocess-browser-shown";
|
||||
|
||||
class nsBrowserElement::BrowserShownObserver : public nsIObserver
|
||||
, public nsSupportsWeakReference
|
||||
{
|
||||
public:
|
||||
BrowserShownObserver(nsBrowserElement* aBrowserElement);
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
void AddObserver();
|
||||
void RemoveObserver();
|
||||
private:
|
||||
virtual ~BrowserShownObserver();
|
||||
|
||||
// Weak reference to the browser element. nsBrowserElement has a
|
||||
// reference to us. nsBrowserElement's destructor is responsible to
|
||||
// null out this weak reference via RemoveObserver()
|
||||
nsBrowserElement* mBrowserElement;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsBrowserElement::BrowserShownObserver, nsIObserver, nsISupportsWeakReference)
|
||||
|
||||
nsBrowserElement::BrowserShownObserver::BrowserShownObserver(nsBrowserElement* aBrowserElement)
|
||||
: mBrowserElement(aBrowserElement)
|
||||
{
|
||||
}
|
||||
|
||||
nsBrowserElement::BrowserShownObserver::~BrowserShownObserver()
|
||||
{
|
||||
RemoveObserver();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBrowserElement::BrowserShownObserver::Observe(nsISupports* aSubject,
|
||||
const char* aTopic,
|
||||
const char16_t* aData)
|
||||
{
|
||||
NS_ENSURE_TRUE(mBrowserElement, NS_OK);
|
||||
|
||||
if (!strcmp(aTopic, kRemoteBrowserPending) ||
|
||||
!strcmp(aTopic, kInprocessBrowserShown)) {
|
||||
nsCOMPtr<nsIFrameLoader> frameLoader = do_QueryInterface(aSubject);
|
||||
nsCOMPtr<nsIFrameLoader> myFrameLoader = mBrowserElement->GetFrameLoader();
|
||||
// The browser element API needs the frameloader to
|
||||
// initialize. We still use the observer to get notified when the
|
||||
// frameloader is created. So we check if the frameloader created
|
||||
// is ours, then initialize the browser element API.
|
||||
if (frameLoader && frameLoader == myFrameLoader) {
|
||||
mBrowserElement->InitBrowserElementAPI();
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsBrowserElement::BrowserShownObserver::AddObserver()
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
||||
if (obs) {
|
||||
obs->AddObserver(this, kRemoteBrowserPending, true);
|
||||
obs->AddObserver(this, kInprocessBrowserShown, true);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsBrowserElement::BrowserShownObserver::RemoveObserver()
|
||||
{
|
||||
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
||||
if (obs) {
|
||||
obs->RemoveObserver(this, kRemoteBrowserPending);
|
||||
obs->RemoveObserver(this, kInprocessBrowserShown);
|
||||
}
|
||||
mBrowserElement = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
nsBrowserElement::IsBrowserElementOrThrow(ErrorResult& aRv)
|
||||
{
|
||||
if (mBrowserElementAPI) {
|
||||
return true;
|
||||
}
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
nsBrowserElement::IsNotWidgetOrThrow(ErrorResult& aRv)
|
||||
{
|
||||
if (!mOwnerIsWidget) {
|
||||
return true;
|
||||
}
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
nsBrowserElement::InitBrowserElementAPI()
|
||||
{
|
||||
bool isBrowserOrApp;
|
||||
nsCOMPtr<nsIFrameLoader> frameLoader = GetFrameLoader();
|
||||
NS_ENSURE_TRUE_VOID(frameLoader);
|
||||
nsresult rv = frameLoader->GetOwnerIsBrowserOrAppFrame(&isBrowserOrApp);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
rv = frameLoader->GetOwnerIsWidget(&mOwnerIsWidget);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
if (!isBrowserOrApp) {
|
||||
return;
|
||||
}
|
||||
|
||||
mBrowserElementAPI = do_CreateInstance("@mozilla.org/dom/browser-element-api;1");
|
||||
if (mBrowserElementAPI) {
|
||||
mBrowserElementAPI->SetFrameLoader(frameLoader);
|
||||
}
|
||||
}
|
||||
|
||||
nsBrowserElement::nsBrowserElement()
|
||||
: mOwnerIsWidget(false)
|
||||
{
|
||||
mObserver = new BrowserShownObserver(this);
|
||||
mObserver->AddObserver();
|
||||
}
|
||||
|
||||
nsBrowserElement::~nsBrowserElement()
|
||||
{
|
||||
mObserver->RemoveObserver();
|
||||
}
|
||||
|
||||
void
|
||||
nsBrowserElement::SetVisible(bool aVisible, ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
|
||||
|
||||
nsresult rv = mBrowserElementAPI->SetVisible(aVisible);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsBrowserElement::GetVisible(ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
|
||||
|
||||
nsCOMPtr<nsIDOMDOMRequest> req;
|
||||
nsresult rv = mBrowserElementAPI->GetVisible(getter_AddRefs(req));
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return req.forget().downcast<DOMRequest>();
|
||||
}
|
||||
|
||||
void
|
||||
nsBrowserElement::SetActive(bool aVisible, ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
|
||||
|
||||
nsresult rv = mBrowserElementAPI->SetActive(aVisible);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
nsBrowserElement::GetActive(ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), false);
|
||||
|
||||
bool isActive;
|
||||
nsresult rv = mBrowserElementAPI->GetActive(&isActive);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return false;
|
||||
}
|
||||
|
||||
return isActive;
|
||||
}
|
||||
|
||||
void
|
||||
nsBrowserElement::SendMouseEvent(const nsAString& aType,
|
||||
uint32_t aX,
|
||||
uint32_t aY,
|
||||
uint32_t aButton,
|
||||
uint32_t aClickCount,
|
||||
uint32_t aModifiers,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
|
||||
NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
|
||||
|
||||
nsresult rv = mBrowserElementAPI->SendMouseEvent(aType,
|
||||
aX,
|
||||
aY,
|
||||
aButton,
|
||||
aClickCount,
|
||||
aModifiers);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsBrowserElement::SendTouchEvent(const nsAString& aType,
|
||||
const Sequence<uint32_t>& aIdentifiers,
|
||||
const Sequence<int32_t>& aXs,
|
||||
const Sequence<int32_t>& aYs,
|
||||
const Sequence<uint32_t>& aRxs,
|
||||
const Sequence<uint32_t>& aRys,
|
||||
const Sequence<float>& aRotationAngles,
|
||||
const Sequence<float>& aForces,
|
||||
uint32_t aCount,
|
||||
uint32_t aModifiers,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
|
||||
NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
|
||||
|
||||
if (aIdentifiers.Length() != aCount ||
|
||||
aXs.Length() != aCount ||
|
||||
aYs.Length() != aCount ||
|
||||
aRxs.Length() != aCount ||
|
||||
aRys.Length() != aCount ||
|
||||
aRotationAngles.Length() != aCount ||
|
||||
aForces.Length() != aCount) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv = mBrowserElementAPI->SendTouchEvent(aType,
|
||||
aIdentifiers.Elements(),
|
||||
aXs.Elements(),
|
||||
aYs.Elements(),
|
||||
aRxs.Elements(),
|
||||
aRys.Elements(),
|
||||
aRotationAngles.Elements(),
|
||||
aForces.Elements(),
|
||||
aCount,
|
||||
aModifiers);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsBrowserElement::GoBack(ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
|
||||
NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
|
||||
|
||||
nsresult rv = mBrowserElementAPI->GoBack();
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsBrowserElement::GoForward(ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
|
||||
NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
|
||||
|
||||
nsresult rv = mBrowserElementAPI->GoForward();
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsBrowserElement::Reload(bool aHardReload, ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
|
||||
NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
|
||||
|
||||
nsresult rv = mBrowserElementAPI->Reload(aHardReload);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsBrowserElement::Stop(ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
|
||||
NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
|
||||
|
||||
nsresult rv = mBrowserElementAPI->Stop();
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsBrowserElement::Download(const nsAString& aUrl,
|
||||
const BrowserElementDownloadOptions& aOptions,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
|
||||
NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
|
||||
|
||||
nsCOMPtr<nsIDOMDOMRequest> req;
|
||||
nsCOMPtr<nsIXPConnectWrappedJS> wrappedObj = do_QueryInterface(mBrowserElementAPI);
|
||||
MOZ_ASSERT(wrappedObj, "Failed to get wrapped JS from XPCOM component.");
|
||||
AutoJSAPI jsapi;
|
||||
jsapi.Init(wrappedObj->GetJSObject());
|
||||
JSContext* cx = jsapi.cx();
|
||||
JS::Rooted<JS::Value> options(cx);
|
||||
if (!ToJSValue(cx, aOptions, &options)) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return nullptr;
|
||||
}
|
||||
nsresult rv = mBrowserElementAPI->Download(aUrl, options, getter_AddRefs(req));
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return req.forget().downcast<DOMRequest>();
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsBrowserElement::PurgeHistory(ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
|
||||
NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
|
||||
|
||||
nsCOMPtr<nsIDOMDOMRequest> req;
|
||||
nsresult rv = mBrowserElementAPI->PurgeHistory(getter_AddRefs(req));
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return req.forget().downcast<DOMRequest>();
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsBrowserElement::GetScreenshot(uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
const nsAString& aMimeType,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
|
||||
NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
|
||||
|
||||
nsCOMPtr<nsIDOMDOMRequest> req;
|
||||
nsresult rv = mBrowserElementAPI->GetScreenshot(aWidth, aHeight, aMimeType,
|
||||
getter_AddRefs(req));
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
if (rv == NS_ERROR_INVALID_ARG) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
|
||||
} else {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return req.forget().downcast<DOMRequest>();
|
||||
}
|
||||
|
||||
void
|
||||
nsBrowserElement::Zoom(float aZoom, ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
|
||||
NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
|
||||
|
||||
nsresult rv = mBrowserElementAPI->Zoom(aZoom);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsBrowserElement::GetCanGoBack(ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
|
||||
NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
|
||||
|
||||
nsCOMPtr<nsIDOMDOMRequest> req;
|
||||
nsresult rv = mBrowserElementAPI->GetCanGoBack(getter_AddRefs(req));
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return req.forget().downcast<DOMRequest>();
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsBrowserElement::GetCanGoForward(ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
|
||||
NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
|
||||
|
||||
nsCOMPtr<nsIDOMDOMRequest> req;
|
||||
nsresult rv = mBrowserElementAPI->GetCanGoForward(getter_AddRefs(req));
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return req.forget().downcast<DOMRequest>();
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsBrowserElement::GetContentDimensions(ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
|
||||
NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
|
||||
|
||||
nsCOMPtr<nsIDOMDOMRequest> req;
|
||||
nsresult rv = mBrowserElementAPI->GetContentDimensions(getter_AddRefs(req));
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return req.forget().downcast<DOMRequest>();
|
||||
}
|
||||
|
||||
void
|
||||
nsBrowserElement::AddNextPaintListener(BrowserElementNextPaintEventCallback& aListener,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
|
||||
|
||||
CallbackObjectHolder<BrowserElementNextPaintEventCallback,
|
||||
nsIBrowserElementNextPaintListener> holder(&aListener);
|
||||
nsCOMPtr<nsIBrowserElementNextPaintListener> listener = holder.ToXPCOMCallback();
|
||||
|
||||
nsresult rv = mBrowserElementAPI->AddNextPaintListener(listener);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsBrowserElement::RemoveNextPaintListener(BrowserElementNextPaintEventCallback& aListener,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
|
||||
|
||||
CallbackObjectHolder<BrowserElementNextPaintEventCallback,
|
||||
nsIBrowserElementNextPaintListener> holder(&aListener);
|
||||
nsCOMPtr<nsIBrowserElementNextPaintListener> listener = holder.ToXPCOMCallback();
|
||||
|
||||
nsresult rv = mBrowserElementAPI->RemoveNextPaintListener(listener);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
nsBrowserElement::SetInputMethodActive(bool aIsActive,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
|
||||
|
||||
nsCOMPtr<nsIFrameLoader> frameLoader = GetFrameLoader();
|
||||
if (!frameLoader) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDOMElement> ownerElement;
|
||||
nsresult rv = frameLoader->GetOwnerElement(getter_AddRefs(ownerElement));
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsINode> node = do_QueryInterface(ownerElement);
|
||||
nsCOMPtr<nsIPrincipal> principal = node->NodePrincipal();
|
||||
if (!nsContentUtils::IsExactSitePermAllow(principal, "input-manage")) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDOMDOMRequest> req;
|
||||
rv = mBrowserElementAPI->SetInputMethodActive(aIsActive,
|
||||
getter_AddRefs(req));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
if (rv == NS_ERROR_INVALID_ARG) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
|
||||
} else {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return req.forget().downcast<DOMRequest>();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -1,109 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef nsBrowserElement_h
|
||||
#define nsBrowserElement_h
|
||||
|
||||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIBrowserElementAPI.h"
|
||||
|
||||
class nsFrameLoader;
|
||||
class nsIObserver;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace dom {
|
||||
struct BrowserElementDownloadOptions;
|
||||
class BrowserElementNextPaintEventCallback;
|
||||
class DOMRequest;
|
||||
} // namespace dom
|
||||
|
||||
class ErrorResult;
|
||||
|
||||
/**
|
||||
* A helper class for browser-element frames
|
||||
*/
|
||||
class nsBrowserElement
|
||||
{
|
||||
public:
|
||||
nsBrowserElement();
|
||||
virtual ~nsBrowserElement();
|
||||
|
||||
void SetVisible(bool aVisible, ErrorResult& aRv);
|
||||
already_AddRefed<dom::DOMRequest> GetVisible(ErrorResult& aRv);
|
||||
void SetActive(bool aActive, ErrorResult& aRv);
|
||||
bool GetActive(ErrorResult& aRv);
|
||||
|
||||
void SendMouseEvent(const nsAString& aType,
|
||||
uint32_t aX,
|
||||
uint32_t aY,
|
||||
uint32_t aButton,
|
||||
uint32_t aClickCount,
|
||||
uint32_t aModifiers,
|
||||
ErrorResult& aRv);
|
||||
void SendTouchEvent(const nsAString& aType,
|
||||
const dom::Sequence<uint32_t>& aIdentifiers,
|
||||
const dom::Sequence<int32_t>& aX,
|
||||
const dom::Sequence<int32_t>& aY,
|
||||
const dom::Sequence<uint32_t>& aRx,
|
||||
const dom::Sequence<uint32_t>& aRy,
|
||||
const dom::Sequence<float>& aRotationAngles,
|
||||
const dom::Sequence<float>& aForces,
|
||||
uint32_t aCount,
|
||||
uint32_t aModifiers,
|
||||
ErrorResult& aRv);
|
||||
void GoBack(ErrorResult& aRv);
|
||||
void GoForward(ErrorResult& aRv);
|
||||
void Reload(bool aHardReload, ErrorResult& aRv);
|
||||
void Stop(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<dom::DOMRequest>
|
||||
Download(const nsAString& aUrl,
|
||||
const dom::BrowserElementDownloadOptions& options,
|
||||
ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<dom::DOMRequest> PurgeHistory(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<dom::DOMRequest>
|
||||
GetScreenshot(uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
const nsAString& aMimeType,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void Zoom(float aZoom, ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<dom::DOMRequest> GetCanGoBack(ErrorResult& aRv);
|
||||
already_AddRefed<dom::DOMRequest> GetCanGoForward(ErrorResult& aRv);
|
||||
already_AddRefed<dom::DOMRequest> GetContentDimensions(ErrorResult& aRv);
|
||||
|
||||
void AddNextPaintListener(dom::BrowserElementNextPaintEventCallback& listener,
|
||||
ErrorResult& aRv);
|
||||
void RemoveNextPaintListener(dom::BrowserElementNextPaintEventCallback& listener,
|
||||
ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<dom::DOMRequest> SetInputMethodActive(bool isActive,
|
||||
ErrorResult& aRv);
|
||||
|
||||
protected:
|
||||
NS_IMETHOD_(already_AddRefed<nsFrameLoader>) GetFrameLoader() = 0;
|
||||
nsCOMPtr<nsIBrowserElementAPI> mBrowserElementAPI;
|
||||
|
||||
private:
|
||||
void InitBrowserElementAPI();
|
||||
bool IsBrowserElementOrThrow(ErrorResult& aRv);
|
||||
bool IsNotWidgetOrThrow(ErrorResult& aRv);
|
||||
bool mOwnerIsWidget;
|
||||
|
||||
class BrowserShownObserver;
|
||||
friend class BrowserShownObserver;
|
||||
nsRefPtr<BrowserShownObserver> mObserver;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // nsBrowserElement_h
|
|
@ -34,7 +34,6 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericHTMLFrameElement)
|
|||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGenericHTMLFrameElement,
|
||||
nsGenericHTMLElement)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameLoader)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserElementAPI)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(nsGenericHTMLFrameElement, nsGenericHTMLElement)
|
||||
|
@ -307,10 +306,6 @@ nsGenericHTMLFrameElement::GetReallyIsBrowserOrApp(bool *aOut)
|
|||
uint32_t permission = nsIPermissionManager::DENY_ACTION;
|
||||
nsresult rv = permMgr->TestPermissionFromPrincipal(principal, "browser", &permission);
|
||||
NS_ENSURE_SUCCESS(rv, NS_OK);
|
||||
if (permission != nsIPermissionManager::ALLOW_ACTION) {
|
||||
rv = permMgr->TestPermissionFromPrincipal(principal, "embed-widgets", &permission);
|
||||
NS_ENSURE_SUCCESS(rv, NS_OK);
|
||||
}
|
||||
*aOut = permission == nsIPermissionManager::ALLOW_ACTION;
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -9,15 +9,14 @@
|
|||
#define nsGenericHTMLFrameElement_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/nsBrowserElement.h"
|
||||
|
||||
#include "nsElementFrameLoaderOwner.h"
|
||||
#include "nsFrameLoader.h"
|
||||
#include "nsGenericHTMLElement.h"
|
||||
#include "nsIDOMEventListener.h"
|
||||
#include "nsIFrameLoader.h"
|
||||
#include "nsIMozBrowserFrame.h"
|
||||
#include "nsIDOMEventListener.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
|
||||
#include "nsFrameLoader.h"
|
||||
|
||||
class nsXULElement;
|
||||
|
||||
|
@ -26,7 +25,6 @@ class nsXULElement;
|
|||
*/
|
||||
class nsGenericHTMLFrameElement : public nsGenericHTMLElement,
|
||||
public nsElementFrameLoaderOwner,
|
||||
public mozilla::nsBrowserElement,
|
||||
public nsIMozBrowserFrame
|
||||
{
|
||||
public:
|
||||
|
@ -34,7 +32,6 @@ public:
|
|||
mozilla::dom::FromParser aFromParser)
|
||||
: nsGenericHTMLElement(aNodeInfo)
|
||||
, nsElementFrameLoaderOwner(aFromParser)
|
||||
, nsBrowserElement()
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -74,19 +71,6 @@ public:
|
|||
|
||||
static bool BrowserFramesEnabled();
|
||||
|
||||
/**
|
||||
* nsIFrameLoaderOwner defines two GetFrameLoader() overloads. One
|
||||
* is XPCOM style interface, the other one is C++ only. "using" pulls
|
||||
* them both in, now GetFrameLoader() is ambiguous because
|
||||
* nsBrowserElement also has GetFrameLoader(). Explicit redefine
|
||||
* GetFrameLoader() to choose nsElementFrameLoaderOwner::GetFrameLoader()
|
||||
*/
|
||||
using nsElementFrameLoaderOwner::GetFrameLoader;
|
||||
NS_IMETHOD_(already_AddRefed<nsFrameLoader>) GetFrameLoader() MOZ_OVERRIDE
|
||||
{
|
||||
return nsElementFrameLoaderOwner::GetFrameLoader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to map a HTML 'scrolling' attribute value to a nsIScrollable
|
||||
* enum value. scrolling="no" (and its synonyms) maps to
|
||||
|
|
|
@ -101,11 +101,16 @@ nsDOMIdentity.prototype = {
|
|||
// September, 2012. Both names will continue to work for the time being,
|
||||
// but code should be changed to use loggedInUser instead.
|
||||
checkRenamed(aOptions, "loggedInEmail", "loggedInUser");
|
||||
message["loggedInUser"] = aOptions["loggedInUser"];
|
||||
|
||||
let emailType = typeof(aOptions["loggedInUser"]);
|
||||
if (aOptions["loggedInUser"] && aOptions["loggedInUser"] !== "undefined") {
|
||||
if (emailType !== "string") {
|
||||
// Bad IPC or IDL converts null and undefined to "null" and "undefined".
|
||||
// We can't assign to aOptions, which complicates the workaround.
|
||||
message["loggedInUser"] = aOptions["loggedInUser"];
|
||||
if (message.loggedInUser == "null" || message.loggedInUser == "undefined") {
|
||||
message.loggedInUser = null;
|
||||
}
|
||||
|
||||
if (message.loggedInUser) {
|
||||
if (typeof(message.loggedInUser) !== "string") {
|
||||
throw new Error("loggedInUser must be a String or null");
|
||||
}
|
||||
|
||||
|
@ -115,8 +120,6 @@ nsDOMIdentity.prototype = {
|
|||
|| aOptions["loggedInUser"].length > MAX_STRING_LENGTH) {
|
||||
throw new Error("loggedInUser is not valid");
|
||||
}
|
||||
// Set loggedInUser in this block that "undefined" doesn't get through.
|
||||
message.loggedInUser = aOptions.loggedInUser;
|
||||
}
|
||||
this._log("loggedInUser: " + message.loggedInUser);
|
||||
|
||||
|
|
|
@ -57,5 +57,17 @@ ASSERT_MOBILE_ROAMING_MODE_EQUALITY(Any, CDMA_ROAMING_PREFERENCE_ANY);
|
|||
|
||||
#undef ASSERT_MOBILE_ROAMING_MODE_EQUALITY
|
||||
|
||||
#define ASSERT_MOBILE_NETWORK_TYPE_EQUALITY(webidlState, xpidlState) \
|
||||
static_assert(static_cast<int32_t>(MobileNetworkType::webidlState) == nsIMobileConnection::xpidlState, \
|
||||
"MobileNetworkType::" #webidlState " should equal to nsIMobileConnection::" #xpidlState)
|
||||
|
||||
ASSERT_MOBILE_NETWORK_TYPE_EQUALITY(Gsm, MOBILE_NETWORK_TYPE_GSM);
|
||||
ASSERT_MOBILE_NETWORK_TYPE_EQUALITY(Wcdma, MOBILE_NETWORK_TYPE_WCDMA);
|
||||
ASSERT_MOBILE_NETWORK_TYPE_EQUALITY(Cdma, MOBILE_NETWORK_TYPE_CDMA);
|
||||
ASSERT_MOBILE_NETWORK_TYPE_EQUALITY(Evdo, MOBILE_NETWORK_TYPE_EVDO);
|
||||
ASSERT_MOBILE_NETWORK_TYPE_EQUALITY(Lte, MOBILE_NETWORK_TYPE_LTE);
|
||||
|
||||
#undef ASSERT_MOBILE_NETWORK_TYPE_EQUALITY
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -378,23 +378,20 @@ MobileConnection::GetSupportedNetworkTypes(nsTArray<MobileNetworkType>& aTypes)
|
|||
return;
|
||||
}
|
||||
|
||||
char16_t** types = nullptr;
|
||||
int32_t* types = nullptr;
|
||||
uint32_t length = 0;
|
||||
|
||||
nsresult rv = mMobileConnection->GetSupportedNetworkTypes(&types, &length);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
nsDependentString rawType(types[i]);
|
||||
Nullable<MobileNetworkType> type = Nullable<MobileNetworkType>();
|
||||
CONVERT_STRING_TO_NULLABLE_ENUM(rawType, MobileNetworkType, type);
|
||||
int32_t type = types[i];
|
||||
|
||||
if (!type.IsNull()) {
|
||||
aTypes.AppendElement(type.Value());
|
||||
}
|
||||
MOZ_ASSERT(type < static_cast<int32_t>(MobileNetworkType::EndGuard_));
|
||||
aTypes.AppendElement(static_cast<MobileNetworkType>(type));
|
||||
}
|
||||
|
||||
NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(length, types);
|
||||
nsMemory::Free(types);
|
||||
}
|
||||
|
||||
already_AddRefed<DOMRequest>
|
||||
|
|
|
@ -398,6 +398,7 @@ MobileConnectionProvider.prototype = {
|
|||
RIL.GECKO_SUPPORTED_NETWORK_TYPES_DEFAULT.split(",");
|
||||
}
|
||||
|
||||
let enumNetworkTypes = [];
|
||||
for (let type of supportedNetworkTypes) {
|
||||
// If the value in system property is not valid, use the default one which
|
||||
// is defined in ril_consts.js.
|
||||
|
@ -405,15 +406,18 @@ MobileConnectionProvider.prototype = {
|
|||
if (DEBUG) {
|
||||
this._debug("Unknown network type: " + type);
|
||||
}
|
||||
supportedNetworkTypes =
|
||||
RIL.GECKO_SUPPORTED_NETWORK_TYPES_DEFAULT.split(",");
|
||||
RIL.GECKO_SUPPORTED_NETWORK_TYPES_DEFAULT.split(",").forEach(aType => {
|
||||
enumNetworkTypes.push(RIL.GECKO_SUPPORTED_NETWORK_TYPES.indexOf(aType));
|
||||
});
|
||||
break;
|
||||
}
|
||||
enumNetworkTypes.push(RIL.GECKO_SUPPORTED_NETWORK_TYPES.indexOf(type));
|
||||
}
|
||||
if (DEBUG) {
|
||||
this._debug("Supported Network Types: " + supportedNetworkTypes);
|
||||
this._debug("Supported Network Types: " + enumNetworkTypes);
|
||||
}
|
||||
return supportedNetworkTypes;
|
||||
|
||||
return enumNetworkTypes;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -235,7 +235,7 @@ already_AddRefed<nsIMobileConnectionService>
|
|||
NS_CreateMobileConnectionService();
|
||||
%}
|
||||
|
||||
[scriptable, uuid(5250a0ba-19a2-4e5b-a5ee-1e69ba2897a9)]
|
||||
[scriptable, uuid(99e43353-5fc4-497e-88a2-5fa6862ee64c)]
|
||||
interface nsIMobileConnection : nsISupports
|
||||
{
|
||||
/*
|
||||
|
@ -335,6 +335,15 @@ interface nsIMobileConnection : nsISupports
|
|||
const long CDMA_ROAMING_PREFERENCE_AFFILIATED = 1;
|
||||
const long CDMA_ROAMING_PREFERENCE_ANY = 2;
|
||||
|
||||
/**
|
||||
* Supported network type.
|
||||
*/
|
||||
const long MOBILE_NETWORK_TYPE_GSM = 0;
|
||||
const long MOBILE_NETWORK_TYPE_WCDMA = 1;
|
||||
const long MOBILE_NETWORK_TYPE_CDMA = 2;
|
||||
const long MOBILE_NETWORK_TYPE_EVDO = 3;
|
||||
const long MOBILE_NETWORK_TYPE_LTE = 4;
|
||||
|
||||
readonly attribute unsigned long serviceId;
|
||||
|
||||
/**
|
||||
|
@ -386,10 +395,9 @@ interface nsIMobileConnection : nsISupports
|
|||
/**
|
||||
* The network types supported by this radio.
|
||||
*
|
||||
* @return an array of DOMString
|
||||
* Possible values: 'gsm', 'wcdma', 'cdma', 'evdo', 'lte'.
|
||||
* @return an array of nsIMobileConnection.MOBILE_NETWORK_TYPE_* values.
|
||||
*/
|
||||
void getSupportedNetworkTypes([array, size_is(length)] out wstring types,
|
||||
void getSupportedNetworkTypes([array, size_is(length)] out long types,
|
||||
[retval] out unsigned long length);
|
||||
|
||||
/**
|
||||
|
|
|
@ -113,7 +113,7 @@ MobileConnectionChild::GetRadioState(int32_t* aRadioState)
|
|||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MobileConnectionChild::GetSupportedNetworkTypes(char16_t*** aTypes,
|
||||
MobileConnectionChild::GetSupportedNetworkTypes(int32_t** aTypes,
|
||||
uint32_t* aLength)
|
||||
{
|
||||
NS_ENSURE_ARG(aTypes);
|
||||
|
@ -121,11 +121,11 @@ MobileConnectionChild::GetSupportedNetworkTypes(char16_t*** aTypes,
|
|||
|
||||
*aLength = mSupportedNetworkTypes.Length();
|
||||
*aTypes =
|
||||
static_cast<char16_t**>(nsMemory::Alloc((*aLength) * sizeof(char16_t*)));
|
||||
static_cast<int32_t*>(nsMemory::Alloc((*aLength) * sizeof(int32_t)));
|
||||
NS_ENSURE_TRUE(*aTypes, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
for (uint32_t i = 0; i < *aLength; i++) {
|
||||
(*aTypes)[i] = ToNewUnicode(mSupportedNetworkTypes[i]);
|
||||
(*aTypes)[i] = mSupportedNetworkTypes[i];
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
|
|
@ -117,7 +117,7 @@ private:
|
|||
nsString mLastNetwork;
|
||||
nsString mLastHomeNetwork;
|
||||
int32_t mNetworkSelectionMode;
|
||||
nsTArray<nsString> mSupportedNetworkTypes;
|
||||
nsTArray<int32_t> mSupportedNetworkTypes;
|
||||
};
|
||||
|
||||
/******************************************************************************
|
||||
|
|
|
@ -131,7 +131,7 @@ MobileConnectionParent::RecvInit(nsMobileConnectionInfo* aVoice,
|
|||
nsString* aIccId,
|
||||
int32_t* aNetworkSelectionMode,
|
||||
int32_t* aRadioState,
|
||||
nsTArray<nsString>* aSupportedNetworkTypes)
|
||||
nsTArray<int32_t>* aSupportedNetworkTypes)
|
||||
{
|
||||
NS_ENSURE_TRUE(mMobileConnection, false);
|
||||
|
||||
|
@ -143,18 +143,17 @@ MobileConnectionParent::RecvInit(nsMobileConnectionInfo* aVoice,
|
|||
NS_ENSURE_SUCCESS(mMobileConnection->GetNetworkSelectionMode(aNetworkSelectionMode), false);
|
||||
NS_ENSURE_SUCCESS(mMobileConnection->GetRadioState(aRadioState), false);
|
||||
|
||||
char16_t** types = nullptr;
|
||||
int32_t* types = nullptr;
|
||||
uint32_t length = 0;
|
||||
|
||||
nsresult rv = mMobileConnection->GetSupportedNetworkTypes(&types, &length);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
nsDependentString type(types[i]);
|
||||
aSupportedNetworkTypes->AppendElement(type);
|
||||
aSupportedNetworkTypes->AppendElement(types[i]);
|
||||
}
|
||||
|
||||
NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(length, types);
|
||||
nsMemory::Free(types);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ protected:
|
|||
RecvInit(nsMobileConnectionInfo* aVoice, nsMobileConnectionInfo* aData,
|
||||
nsString* aLastKnownNetwork, nsString* aLastKnownHomeNetwork,
|
||||
nsString* aIccId, int32_t* aNetworkSelectionMode,
|
||||
int32_t* aRadioState, nsTArray<nsString>* aSupportedNetworkTypes) MOZ_OVERRIDE;
|
||||
int32_t* aRadioState, nsTArray<int32_t>* aSupportedNetworkTypes) MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsIMobileConnection> mMobileConnection;
|
||||
|
|
|
@ -51,7 +51,7 @@ parent:
|
|||
returns (nsMobileConnectionInfo aVoice, nsMobileConnectionInfo aData,
|
||||
nsString aLastKnownNetwork, nsString aLastKnownHomeNetwork,
|
||||
nsString aIccId, int32_t aNetworkSelectionMode,
|
||||
int32_t aRadioState, nsString[] aSupportedNetworkTypes);
|
||||
int32_t aRadioState, int32_t[] aSupportedNetworkTypes);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -427,6 +427,7 @@ this.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO = [
|
|||
];
|
||||
|
||||
this.GECKO_SUPPORTED_NETWORK_TYPES_DEFAULT = "gsm,wcdma";
|
||||
// Index-item pair must be in sync with nsIMobileConnection.MOBILE_NETWORK_TYPE_*
|
||||
this.GECKO_SUPPORTED_NETWORK_TYPES = [
|
||||
"gsm",
|
||||
"wcdma",
|
||||
|
|
|
@ -1,142 +0,0 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
callback BrowserElementNextPaintEventCallback = void ();
|
||||
|
||||
dictionary BrowserElementDownloadOptions {
|
||||
DOMString? filename;
|
||||
};
|
||||
|
||||
[NoInterfaceObject]
|
||||
interface BrowserElement {
|
||||
};
|
||||
|
||||
BrowserElement implements BrowserElementCommon;
|
||||
BrowserElement implements BrowserElementPrivileged;
|
||||
|
||||
[NoInterfaceObject]
|
||||
interface BrowserElementCommon {
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser embed-widgets"]
|
||||
void setVisible(boolean visible);
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser embed-widgets"]
|
||||
DOMRequest getVisible();
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser embed-widgets"]
|
||||
void setActive(boolean active);
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser embed-widgets"]
|
||||
boolean getActive();
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser embed-widgets"]
|
||||
void addNextPaintListener(BrowserElementNextPaintEventCallback listener);
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser embed-widgets"]
|
||||
void removeNextPaintListener(BrowserElementNextPaintEventCallback listener);
|
||||
};
|
||||
|
||||
[NoInterfaceObject]
|
||||
interface BrowserElementPrivileged {
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser"]
|
||||
void sendMouseEvent(DOMString type,
|
||||
unsigned long x,
|
||||
unsigned long y,
|
||||
unsigned long button,
|
||||
unsigned long clickCount,
|
||||
unsigned long modifiers);
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
Func="TouchEvent::PrefEnabled",
|
||||
CheckPermissions="browser"]
|
||||
void sendTouchEvent(DOMString type,
|
||||
sequence<unsigned long> identifiers,
|
||||
sequence<long> x,
|
||||
sequence<long> y,
|
||||
sequence<unsigned long> rx,
|
||||
sequence<unsigned long> ry,
|
||||
sequence<float> rotationAngles,
|
||||
sequence<float> forces,
|
||||
unsigned long count,
|
||||
unsigned long modifiers);
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser"]
|
||||
void goBack();
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser"]
|
||||
void goForward();
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser"]
|
||||
void reload(optional boolean hardReload = false);
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser"]
|
||||
void stop();
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser"]
|
||||
DOMRequest download(DOMString url,
|
||||
optional BrowserElementDownloadOptions options);
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser"]
|
||||
DOMRequest purgeHistory();
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser"]
|
||||
DOMRequest getScreenshot([EnforceRange] unsigned long width,
|
||||
[EnforceRange] unsigned long height,
|
||||
optional DOMString mimeType="");
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser"]
|
||||
void zoom(float zoom);
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser"]
|
||||
DOMRequest getCanGoBack();
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser"]
|
||||
DOMRequest getCanGoForward();
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser"]
|
||||
DOMRequest getContentDimensions();
|
||||
|
||||
[Throws,
|
||||
Pref="dom.mozBrowserFramesEnabled",
|
||||
CheckPermissions="browser"]
|
||||
DOMRequest setInputMethodActive(boolean isActive);
|
||||
};
|
|
@ -63,4 +63,3 @@ partial interface HTMLIFrameElement {
|
|||
};
|
||||
|
||||
HTMLIFrameElement implements MozFrameLoaderOwner;
|
||||
HTMLIFrameElement implements BrowserElement;
|
||||
|
|
|
@ -55,7 +55,6 @@ WEBIDL_FILES = [
|
|||
'BiquadFilterNode.webidl',
|
||||
'Blob.webidl',
|
||||
'BoxObject.webidl',
|
||||
'BrowserElement.webidl',
|
||||
'BrowserElementDictionaries.webidl',
|
||||
'CallsList.webidl',
|
||||
'CameraCapabilities.webidl',
|
||||
|
|
|
@ -117,7 +117,6 @@
|
|||
@BINPATH@/components/autocomplete.xpt
|
||||
@BINPATH@/components/autoconfig.xpt
|
||||
@BINPATH@/components/browsercompsbase.xpt
|
||||
@BINPATH@/components/browser-element.xpt
|
||||
@BINPATH@/components/browser-feeds.xpt
|
||||
@BINPATH@/components/caps.xpt
|
||||
@BINPATH@/components/chardet.xpt
|
||||
|
|
|
@ -19,6 +19,7 @@ import android.os.Bundle;
|
|||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
/**
|
||||
|
@ -105,4 +106,13 @@ public class SearchPreferenceActivity extends PreferenceActivity {
|
|||
};
|
||||
clearHistoryTask.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -262,7 +262,7 @@ this.HawkClient.prototype = {
|
|||
};
|
||||
|
||||
let request = this.newHAWKAuthenticatedRESTRequest(uri, credentials, extra);
|
||||
if (method == "post" || method == "put") {
|
||||
if (method == "post" || method == "put" || method == "patch") {
|
||||
request[method](payloadObj, onComplete);
|
||||
} else {
|
||||
request[method](onComplete);
|
||||
|
|
|
@ -67,7 +67,7 @@ HAWKAuthenticatedRESTRequest.prototype = {
|
|||
|
||||
dispatch: function dispatch(method, data, onComplete, onProgress) {
|
||||
let contentType = "text/plain";
|
||||
if (method == "POST" || method == "PUT") {
|
||||
if (method == "POST" || method == "PUT" || method == "PATCH") {
|
||||
contentType = "application/json";
|
||||
}
|
||||
if (this.credentials) {
|
||||
|
|
|
@ -207,6 +207,23 @@ RESTRequest.prototype = {
|
|||
return this.dispatch("GET", null, onComplete, onProgress);
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform an HTTP PATCH.
|
||||
*
|
||||
* @param data
|
||||
* Data to be used as the request body. If this isn't a string
|
||||
* it will be JSONified automatically.
|
||||
* @param onComplete
|
||||
* Short-circuit way to set the 'onComplete' method. Optional.
|
||||
* @param onProgress
|
||||
* Short-circuit way to set the 'onProgress' method. Optional.
|
||||
*
|
||||
* @return the request object.
|
||||
*/
|
||||
patch: function patch(data, onComplete, onProgress) {
|
||||
return this.dispatch("PATCH", data, onComplete, onProgress);
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform an HTTP PUT.
|
||||
*
|
||||
|
@ -307,7 +324,7 @@ RESTRequest.prototype = {
|
|||
}
|
||||
|
||||
// Set HTTP request body.
|
||||
if (method == "PUT" || method == "POST") {
|
||||
if (method == "PUT" || method == "POST" || method == "PATCH") {
|
||||
// Convert non-string bodies into JSON.
|
||||
if (typeof data != "string") {
|
||||
data = JSON.stringify(data);
|
||||
|
@ -366,7 +383,7 @@ RESTRequest.prototype = {
|
|||
Cr.NS_ERROR_NET_TIMEOUT);
|
||||
if (!this.onComplete) {
|
||||
this._log.error("Unexpected error: onComplete not defined in " +
|
||||
"abortTimeout.")
|
||||
"abortTimeout.");
|
||||
return;
|
||||
}
|
||||
this.onComplete(error);
|
||||
|
|
|
@ -66,9 +66,7 @@ add_task(function test_authenticated_get_request() {
|
|||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function test_authenticated_post_request() {
|
||||
let method = "POST";
|
||||
|
||||
function check_authenticated_request(method) {
|
||||
let server = httpd_setup({"/foo": (request, response) => {
|
||||
do_check_true(request.hasHeader("Authorization"));
|
||||
|
||||
|
@ -86,6 +84,18 @@ add_task(function test_authenticated_post_request() {
|
|||
do_check_eq("bar", result.foo);
|
||||
|
||||
yield deferredStop(server);
|
||||
}
|
||||
|
||||
add_task(function test_authenticated_post_request() {
|
||||
check_authenticated_request("POST");
|
||||
});
|
||||
|
||||
add_task(function test_authenticated_put_request() {
|
||||
check_authenticated_request("PUT");
|
||||
});
|
||||
|
||||
add_task(function test_authenticated_patch_request() {
|
||||
check_authenticated_request("PATCH");
|
||||
});
|
||||
|
||||
add_task(function test_credentials_optional() {
|
||||
|
|
|
@ -271,9 +271,10 @@ add_test(function test_charsets() {
|
|||
});
|
||||
|
||||
/**
|
||||
* Test HTTP PUT with a simple string argument and default Content-Type.
|
||||
* Used for testing PATCH/PUT/POST methods.
|
||||
*/
|
||||
add_test(function test_put() {
|
||||
function check_posting_data(method) {
|
||||
let funcName = method.toLowerCase();
|
||||
let handler = httpd_handler(200, "OK", "Got it!");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
|
@ -299,7 +300,7 @@ add_test(function test_put() {
|
|||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(this.response.body, "Got it!");
|
||||
|
||||
do_check_eq(handler.request.method, "PUT");
|
||||
do_check_eq(handler.request.method, method);
|
||||
do_check_eq(handler.request.body, "Hullo?");
|
||||
do_check_eq(handler.request.getHeader("Content-Type"), "text/plain");
|
||||
|
||||
|
@ -311,61 +312,33 @@ add_test(function test_put() {
|
|||
});
|
||||
};
|
||||
|
||||
do_check_eq(request.put("Hullo?", onComplete, onProgress), request);
|
||||
do_check_eq(request[funcName]("Hullo?", onComplete, onProgress), request);
|
||||
do_check_eq(request.status, request.SENT);
|
||||
do_check_eq(request.method, "PUT");
|
||||
do_check_eq(request.method, method);
|
||||
do_check_throws(function () {
|
||||
request.put("Hai!");
|
||||
request[funcName]("Hai!");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test HTTP PATCH with a simple string argument and default Content-Type.
|
||||
*/
|
||||
add_test(function test_patch() {
|
||||
check_posting_data("PATCH");
|
||||
});
|
||||
|
||||
/**
|
||||
* Test HTTP PUT with a simple string argument and default Content-Type.
|
||||
*/
|
||||
add_test(function test_put() {
|
||||
check_posting_data("PUT");
|
||||
});
|
||||
|
||||
/**
|
||||
* Test HTTP POST with a simple string argument and default Content-Type.
|
||||
*/
|
||||
add_test(function test_post() {
|
||||
let handler = httpd_handler(200, "OK", "Got it!");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let request = new RESTRequest(server.baseURI + "/resource");
|
||||
do_check_eq(request.status, request.NOT_SENT);
|
||||
|
||||
request.onProgress = request.onComplete = function () {
|
||||
do_throw("This function should have been overwritten!");
|
||||
};
|
||||
|
||||
let onProgress_called = false;
|
||||
function onProgress() {
|
||||
onProgress_called = true;
|
||||
do_check_eq(this.status, request.IN_PROGRESS);
|
||||
do_check_true(this.response.body.length > 0);
|
||||
};
|
||||
|
||||
function onComplete(error) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
do_check_eq(this.status, this.COMPLETED);
|
||||
do_check_true(this.response.success);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(this.response.body, "Got it!");
|
||||
|
||||
do_check_eq(handler.request.method, "POST");
|
||||
do_check_eq(handler.request.body, "Hullo?");
|
||||
do_check_eq(handler.request.getHeader("Content-Type"), "text/plain");
|
||||
|
||||
do_check_true(onProgress_called);
|
||||
CommonUtils.nextTick(function () {
|
||||
do_check_eq(request.onComplete, null);
|
||||
do_check_eq(request.onProgress, null);
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
};
|
||||
|
||||
do_check_eq(request.post("Hullo?", onComplete, onProgress), request);
|
||||
do_check_eq(request.status, request.SENT);
|
||||
do_check_eq(request.method, "POST");
|
||||
do_check_throws(function () {
|
||||
request.post("Hai!");
|
||||
});
|
||||
check_posting_data("POST");
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,7 +28,6 @@ skip-if = toolkit == 'gonk'
|
|||
[test_bagheera_server.js]
|
||||
[test_bagheera_client.js]
|
||||
[test_hawkclient.js]
|
||||
run-if = fxaccounts # This test imports some FxAccounts modules.
|
||||
[test_hawkrequest.js]
|
||||
[test_observers.js]
|
||||
[test_restrequest.js]
|
||||
|
|
|
@ -702,6 +702,9 @@ SyncEngine.prototype = {
|
|||
_recordObj: CryptoWrapper,
|
||||
version: 1,
|
||||
|
||||
// Which sortindex to use when retrieving records for this engine.
|
||||
_defaultSort: undefined,
|
||||
|
||||
// A relative priority to use when computing an order
|
||||
// for engines to be synced. Higher-priority engines
|
||||
// (lower numbers) are synced first.
|
||||
|
@ -929,6 +932,10 @@ SyncEngine.prototype = {
|
|||
newitems = this._itemSource();
|
||||
}
|
||||
|
||||
if (this._defaultSort) {
|
||||
newitems.sort = this._defaultSort;
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
batchSize = MOBILE_BATCH_SIZE;
|
||||
}
|
||||
|
|
|
@ -202,6 +202,7 @@ BookmarksEngine.prototype = {
|
|||
_storeObj: BookmarksStore,
|
||||
_trackerObj: BookmarksTracker,
|
||||
version: 2,
|
||||
_defaultSort: "index",
|
||||
|
||||
syncPriority: 4,
|
||||
|
||||
|
|
|
@ -29,7 +29,11 @@ ClientsRec.prototype = {
|
|||
ttl: CLIENTS_TTL
|
||||
};
|
||||
|
||||
Utils.deferGetSet(ClientsRec, "cleartext", ["name", "type", "commands", "version", "protocols"]);
|
||||
Utils.deferGetSet(ClientsRec,
|
||||
"cleartext",
|
||||
["name", "type", "commands",
|
||||
"version", "protocols",
|
||||
"formfactor", "os", "appPackage", "application", "device"]);
|
||||
|
||||
|
||||
this.ClientEngine = function ClientEngine(service) {
|
||||
|
@ -100,6 +104,11 @@ ClientEngine.prototype = {
|
|||
},
|
||||
set localID(value) Svc.Prefs.set("client.GUID", value),
|
||||
|
||||
get brandName() {
|
||||
let brand = new StringBundle("chrome://branding/locale/brand.properties");
|
||||
return brand.get("brandShortName");
|
||||
},
|
||||
|
||||
get localName() {
|
||||
let localName = Svc.Prefs.get("client.name", "");
|
||||
if (localName != "")
|
||||
|
@ -111,9 +120,8 @@ ClientEngine.prototype = {
|
|||
let user = env.get("USER") || env.get("USERNAME") ||
|
||||
Svc.Prefs.get("account") || Svc.Prefs.get("username");
|
||||
|
||||
let brandName = this.brandName;
|
||||
let appName;
|
||||
let brand = new StringBundle("chrome://branding/locale/brand.properties");
|
||||
let brandName = brand.get("brandShortName");
|
||||
try {
|
||||
let syncStrings = new StringBundle("chrome://browser/locale/sync.properties");
|
||||
appName = syncStrings.getFormattedString("sync.defaultAccountApplication", [brandName]);
|
||||
|
@ -412,9 +420,18 @@ ClientStore.prototype = {
|
|||
record.commands = this.engine.localCommands;
|
||||
record.version = Services.appinfo.version;
|
||||
record.protocols = SUPPORTED_PROTOCOL_VERSIONS;
|
||||
}
|
||||
else
|
||||
|
||||
// Optional fields.
|
||||
record.os = Services.appinfo.OS; // "Darwin"
|
||||
record.appPackage = Services.appinfo.ID;
|
||||
record.application = this.engine.brandName // "Nightly"
|
||||
|
||||
// We can't compute these yet.
|
||||
// record.device = ""; // Bug 1100723
|
||||
// record.formfactor = ""; // Bug 1100722
|
||||
} else {
|
||||
record.cleartext = this._remoteClients[id];
|
||||
}
|
||||
|
||||
return record;
|
||||
},
|
||||
|
|
|
@ -577,6 +577,32 @@ add_test(function test_receive_display_uri() {
|
|||
do_check_true(engine.processIncomingCommands());
|
||||
});
|
||||
|
||||
add_test(function test_optional_client_fields() {
|
||||
_("Ensure that we produce records with the fields added in Bug 1097222.");
|
||||
|
||||
const SUPPORTED_PROTOCOL_VERSIONS = ["1.1", "1.5"];
|
||||
let local = engine._store.createRecord(engine.localID, "clients");
|
||||
do_check_eq(local.name, engine.localName);
|
||||
do_check_eq(local.type, engine.localType);
|
||||
do_check_eq(local.version, Services.appinfo.version);
|
||||
do_check_array_eq(local.protocols, SUPPORTED_PROTOCOL_VERSIONS);
|
||||
|
||||
// Optional fields.
|
||||
// Make sure they're what they ought to be...
|
||||
do_check_eq(local.os, Services.appinfo.OS);
|
||||
do_check_eq(local.appPackage, Services.appinfo.ID);
|
||||
|
||||
// ... and also that they're non-empty.
|
||||
do_check_true(!!local.os);
|
||||
do_check_true(!!local.appPackage);
|
||||
do_check_true(!!local.application);
|
||||
|
||||
// We don't currently populate device or formfactor.
|
||||
// See Bug 1100722, Bug 1100723.
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
initTestLogging("Trace");
|
||||
Log.repository.getLogger("Sync.Engine.Clients").level = Log.Level.Trace;
|
||||
|
|
|
@ -3793,16 +3793,23 @@ SearchService.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
// Array for the remaining engines, alphabetically sorted
|
||||
var alphaEngines = [];
|
||||
// Array for the remaining engines, alphabetically sorted.
|
||||
let alphaEngines = [];
|
||||
|
||||
for each (engine in this._engines) {
|
||||
if (!(engine.name in addedEngines))
|
||||
alphaEngines.push(this._engines[engine.name]);
|
||||
}
|
||||
alphaEngines = alphaEngines.sort(function (a, b) {
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
let locale = Cc["@mozilla.org/intl/nslocaleservice;1"]
|
||||
.getService(Ci.nsILocaleService)
|
||||
.newLocale(getLocale());
|
||||
let collation = Cc["@mozilla.org/intl/collation-factory;1"]
|
||||
.createInstance(Ci.nsICollationFactory)
|
||||
.CreateCollation(locale);
|
||||
const strength = Ci.nsICollation.kCollationCaseInsensitiveAscii;
|
||||
let comparator = (a, b) => collation.compareString(strength, a.name, b.name);
|
||||
alphaEngines.sort(comparator);
|
||||
return this.__sortedEngines = this.__sortedEngines.concat(alphaEngines);
|
||||
},
|
||||
|
||||
|
|
|
@ -38,9 +38,13 @@ FocusSyncHandler.init();
|
|||
|
||||
let WebProgressListener = {
|
||||
init: function() {
|
||||
this._filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
|
||||
.createInstance(Ci.nsIWebProgress);
|
||||
this._filter.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_ALL);
|
||||
|
||||
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress);
|
||||
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_ALL);
|
||||
webProgress.addProgressListener(this._filter, Ci.nsIWebProgress.NOTIFY_ALL);
|
||||
},
|
||||
|
||||
_requestSpec: function (aRequest, aPropertyName) {
|
||||
|
@ -50,12 +54,18 @@ let WebProgressListener = {
|
|||
},
|
||||
|
||||
_setupJSON: function setupJSON(aWebProgress, aRequest) {
|
||||
if (aWebProgress) {
|
||||
aWebProgress = {
|
||||
isTopLevel: aWebProgress.isTopLevel,
|
||||
isLoadingDocument: aWebProgress.isLoadingDocument,
|
||||
loadType: aWebProgress.loadType
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isTopLevel: aWebProgress.isTopLevel,
|
||||
isLoadingDocument: aWebProgress.isLoadingDocument,
|
||||
webProgress: aWebProgress || null,
|
||||
requestURI: this._requestSpec(aRequest, "URI"),
|
||||
originalRequestURI: this._requestSpec(aRequest, "originalURI"),
|
||||
loadType: aWebProgress.loadType,
|
||||
documentContentType: content.document && content.document.contentType
|
||||
};
|
||||
},
|
||||
|
@ -64,7 +74,7 @@ let WebProgressListener = {
|
|||
return {
|
||||
contentWindow: content,
|
||||
// DOMWindow is not necessarily the content-window with subframes.
|
||||
DOMWindow: aWebProgress.DOMWindow
|
||||
DOMWindow: aWebProgress && aWebProgress.DOMWindow
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -92,7 +102,7 @@ let WebProgressListener = {
|
|||
json.canGoBack = docShell.canGoBack;
|
||||
json.canGoForward = docShell.canGoForward;
|
||||
|
||||
if (json.isTopLevel) {
|
||||
if (aWebProgress && aWebProgress.isTopLevel) {
|
||||
json.documentURI = content.document.documentURIObject.spec;
|
||||
json.charset = content.document.characterSet;
|
||||
json.mayEnableCharacterEncodingMenu = docShell.mayEnableCharacterEncodingMenu;
|
||||
|
|
|
@ -269,7 +269,9 @@ DevToolsLoader.prototype = {
|
|||
* @see setProvider
|
||||
*/
|
||||
require: function() {
|
||||
this._chooseProvider();
|
||||
if (!this._provider) {
|
||||
this._chooseProvider();
|
||||
}
|
||||
return this.require.apply(this, arguments);
|
||||
},
|
||||
|
||||
|
|
|
@ -1801,6 +1801,11 @@ BrowserAddonActor.prototype = {
|
|||
url: this.url,
|
||||
debuggable: this._addon.isDebuggable,
|
||||
consoleActor: this._consoleActor.actorID,
|
||||
|
||||
traits: {
|
||||
highlightable: false,
|
||||
networkMonitor: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче