Merge mozilla-central to b2g-inbound

This commit is contained in:
Carsten "Tomcat" Book 2014-11-18 17:37:40 +01:00
commit 1150838551
512 changed files with 12385 additions and 13131 deletions

View File

@ -451,6 +451,18 @@ AndroidPresenter.prototype.liveRegion =
UtteranceGenerator.genForLiveRegion(aContext, aIsHide, aModifiedText));
};
AndroidPresenter.prototype.noMove =
function AndroidPresenter_noMove(aMoveMethod) {
return {
type: this.type,
details: [
{ eventType: this.ANDROID_VIEW_ACCESSIBILITY_FOCUSED,
exitView: aMoveMethod,
text: ['']
}]
};
};
/**
* A B2G presenter for Gaia.
*/

View File

@ -709,12 +709,12 @@
</p>
<p id="area17">
<span style="-moz-text-decoration-line: underline;">underline
</span><span style="text-decoration: underline; -moz-text-decoration-color: blue;">blue
</span><span style="text-decoration: underline; -moz-text-decoration-style: dotted;">dotted
</span><span style="-moz-text-decoration-line: line-through;">linethrough
</span><span style="text-decoration: line-through; -moz-text-decoration-color: blue;">blue
</span><span style="text-decoration: line-through; -moz-text-decoration-style: wavy;">wavy
<span style="text-decoration-line: underline;">underline
</span><span style="text-decoration: underline; text-decoration-color: blue;">blue
</span><span style="text-decoration: underline; text-decoration-style: dotted;">dotted
</span><span style="text-decoration-line: line-through;">linethrough
</span><span style="text-decoration: line-through; text-decoration-color: blue;">blue
</span><span style="text-decoration: line-through; text-decoration-style: wavy;">wavy
</span>
</p>

View File

@ -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',

View File

@ -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'];

View File

@ -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);
}
});

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -10,7 +10,6 @@ ac_add_options --with-gonk="$topsrcdir/gonk-toolchain"
export TOOLCHAIN_HOST=linux-x86
export GONK_PRODUCT=generic
ac_add_options --with-gonk-toolchain-prefix="$topsrcdir/gonk-toolchain/prebuilt/$TOOLCHAIN_HOST/toolchain/arm-linux-androideabi-4.4.x/bin/arm-linux-androideabi-"
ac_add_options --disable-elf-hack
ac_add_options --enable-debug-symbols
ac_add_options --enable-debug
#. "$topsrcdir/build/mozconfig.cache"

View File

@ -11,7 +11,6 @@ ac_add_options --with-gonk="$topsrcdir/gonk-toolchain"
export TOOLCHAIN_HOST=linux-x86
export GONK_PRODUCT=generic
ac_add_options --with-gonk-toolchain-prefix="$topsrcdir/gonk-toolchain/prebuilt/$TOOLCHAIN_HOST/toolchain/arm-linux-androideabi-4.4.x/bin/arm-linux-androideabi-"
ac_add_options --disable-elf-hack
ac_add_options --enable-debug-symbols
# ac_add_options --enable-profiling
#. "$topsrcdir/build/mozconfig.cache"

View File

@ -29,7 +29,6 @@ no_sccache=
#B2G options
ac_add_options --enable-application=b2g
ENABLE_MARIONETTE=1
ac_add_options --disable-elf-hack
export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
GAIADIR=$topsrcdir/gaia

View File

@ -27,7 +27,6 @@ no_sccache=
#B2G options
ac_add_options --enable-application=b2g
ac_add_options --disable-elf-hack
export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
GAIADIR=$topsrcdir/gaia

View File

@ -29,7 +29,6 @@ no_sccache=
#B2G options
ac_add_options --enable-application=b2g
ENABLE_MARIONETTE=1
ac_add_options --disable-elf-hack
export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
GAIADIR=$topsrcdir/gaia

View File

@ -27,7 +27,6 @@ no_sccache=
#B2G options
ac_add_options --enable-application=b2g
ac_add_options --disable-elf-hack
export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
GAIADIR=$topsrcdir/gaia

View File

@ -83,9 +83,6 @@ ifdef GKMEDIAS_SHARED_LIBRARY
DEFINES += -DGKMEDIAS_SHARED_LIBRARY
endif
ifdef MOZ_REPLACE_MALLOC
DEFINES += -DMOZ_REPLACE_MALLOC
endif
ifdef MOZ_JEMALLOC3
DEFINES += -DMOZ_JEMALLOC3
endif

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -21,6 +21,7 @@ skip-if = os == "mac" # Intermittent failures, bug 898317
[browser_newtab_bug998387.js]
[browser_newtab_disable.js]
[browser_newtab_drag_drop.js]
skip-if = os == "win" && debug # bug 1097056; test fails in --run-by-dir mode on win8 x64 debug
[browser_newtab_drag_drop_ext.js]
[browser_newtab_drop_preview.js]
[browser_newtab_enhanced.js]

View File

@ -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;
},

View File

@ -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]

View File

@ -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();
});

View File

@ -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) => {

View File

@ -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")),

View File

@ -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>

View File

@ -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
}),

View File

@ -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));
},
/**

View File

@ -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);

View File

@ -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) {

View File

@ -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();

View File

@ -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));
});
});
});
});

View File

@ -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");
});
});
});

View File

@ -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");

View File

@ -36,4 +36,4 @@ skip-if = e10s # Bug ?????? - Test uses load event and checks event.target.
[browser_yahoo_behavior.js]
skip-if = e10s # Bug ?????? - some issue with progress listeners [JavaScript Error: "req.originalURI is null" {file: "chrome://mochitests/content/browser/browser/components/search/test/browser_bing_behavior.js" line: 127}]
[browser_abouthome_behavior.js]
skip-if = e10s # Bug ???????
skip-if = e10s || true # Bug ??????, Bug 1100301 - leaks windows until shutdown when --run-by-dir

View File

@ -19,7 +19,7 @@ support-files =
[browser_tabview_bug587231.js]
skip-if = buildapp == 'mulet'
[browser_tabview_bug587276.js]
skip-if = e10s # Bug 1091200
skip-if = e10s || true # Bug 1091200, bug 1096285
[browser_tabview_bug587351.js]
[browser_tabview_bug587503.js]
[browser_tabview_bug587990.js]

View File

@ -2,7 +2,6 @@
ac_add_options --enable-signmar
ac_add_options --enable-profiling
ac_add_options --disable-elf-hack # --enable-elf-hack conflicts with --enable-profiling
# Nightlies only since this has a cost in performance
ac_add_options --enable-js-diagnostics

View File

@ -5,7 +5,6 @@ no_sccache=1
ac_add_options --enable-valgrind
ac_add_options --disable-jemalloc
ac_add_options --disable-elf-hack
ac_add_options --enable-optimize="-g -O -freorder-blocks"
ac_add_options --disable-install-strip

View File

@ -2,7 +2,6 @@
ac_add_options --enable-signmar
ac_add_options --enable-profiling
ac_add_options --disable-elf-hack # --enable-elf-hack conflicts with --enable-profiling
# Nightlies only since this has a cost in performance
ac_add_options --enable-js-diagnostics

View File

@ -5,7 +5,6 @@ no_sccache=1
ac_add_options --enable-valgrind
ac_add_options --disable-jemalloc
ac_add_options --disable-elf-hack
ac_add_options --enable-optimize="-g -O -freorder-blocks"
ac_add_options --disable-install-strip

View File

@ -34,7 +34,6 @@ whitelist['nightly']['linux32'] += [
'export MOZ_TELEMETRY_REPORTING=1',
"mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
'STRIP_FLAGS="--strip-debug"',
'ac_add_options --disable-elf-hack # --enable-elf-hack conflicts with --enable-profiling',
]
whitelist['nightly']['linux64'] += [
@ -44,7 +43,6 @@ whitelist['nightly']['linux64'] += [
'STRIP_FLAGS="--strip-debug"',
'ac_add_options --with-ccache=/usr/bin/ccache',
'. "$topsrcdir/build/mozconfig.cache"',
'ac_add_options --disable-elf-hack # --enable-elf-hack conflicts with --enable-profiling',
]
whitelist['nightly']['macosx-universal'] += [

View File

@ -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 {

View File

@ -354,43 +354,43 @@ skip-if = e10s
[browser_dbg_scripts-switching-03.js]
skip-if = e10s
[browser_dbg_search-autofill-identifier.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_search-basic-01.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_search-basic-02.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_search-basic-03.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_search-basic-04.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_search-global-01.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_search-global-02.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_search-global-03.js]
skip-if = e10s
skip-if = e10s # Bug 1093535
[browser_dbg_search-global-04.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_search-global-05.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_search-global-06.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_search-popup-jank.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_search-sources-01.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_search-sources-02.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_search-sources-03.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_search-symbols.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_searchbox-help-popup-01.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_searchbox-help-popup-02.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_searchbox-parse.js]
skip-if = e10s
skip-if = e10s && debug
[browser_dbg_source-maps-01.js]
skip-if = e10s && debug
[browser_dbg_source-maps-02.js]

View File

@ -10,7 +10,7 @@
function test() {
const TAB_URL = EXAMPLE_URL + "doc_function-search.html";
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
let Source = 'code_function-search-01.js';
let Debugger = aPanel.panelWin;
let Editor = Debugger.DebuggerView.editor;

View File

@ -7,13 +7,12 @@
const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gEditor, gSources, gFiltering, gSearchBox;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
@ -309,7 +308,6 @@ function performTest() {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;

View File

@ -7,13 +7,12 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gSources, gSearchBox;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSources = gDebugger.DebuggerView.Sources;
@ -33,7 +32,7 @@ function test() {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
gDebuggee.firstCall();
callInTab(gTab, "firstCall");
});
}
@ -116,7 +115,6 @@ function verifySourceAndCaret(aUrl, aLine, aColumn, aSelection) {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gSources = null;

View File

@ -8,13 +8,12 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gSources, gSearchBox;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSources = gDebugger.DebuggerView.Sources;
@ -37,7 +36,7 @@ function test() {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
gDebuggee.firstCall();
callInTab(gTab, "firstCall");
});
}
@ -112,7 +111,6 @@ function verifySourceAndCaret(aUrl, aLine, aColumn) {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gSources = null;

View File

@ -8,13 +8,12 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gEditor, gSources, gSearchBox;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
@ -123,7 +122,6 @@ function testTokenSearch() {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;

View File

@ -8,13 +8,12 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gEditor, gSources, gSearchView, gSearchBox;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
@ -31,7 +30,7 @@ function test() {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
gDebuggee.firstCall();
callInTab(gTab, "firstCall");
});
}
@ -264,7 +263,6 @@ function clearSearch() {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;

View File

@ -8,13 +8,12 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gEditor, gSources, gSearchView, gSearchBox;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
@ -34,7 +33,7 @@ function test() {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
gDebuggee.firstCall();
callInTab(gTab, "firstCall");
});
}
@ -208,7 +207,6 @@ function testSearchTokenEmpty() {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;

View File

@ -8,13 +8,12 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gEditor, gSources, gSearchView, gSearchBox;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
@ -30,7 +29,7 @@ function test() {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
gDebuggee.firstCall();
callInTab(gTab, "firstCall");
});
}
@ -96,7 +95,6 @@ function performTest() {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;

View File

@ -8,13 +8,12 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gEditor, gSources, gSearchView, gSearchBox;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
@ -30,7 +29,7 @@ function test() {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
gDebuggee.firstCall();
callInTab(gTab, "firstCall");
});
}
@ -84,7 +83,6 @@ function secondSearch() {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;

View File

@ -9,13 +9,12 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gEditor, gSources, gSearchView, gSearchBox;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
@ -33,7 +32,7 @@ function test() {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
gDebuggee.firstCall();
callInTab(gTab, "firstCall");
});
}
@ -146,7 +145,6 @@ function testClickMatchToJump() {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;

View File

@ -8,13 +8,12 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gEditor, gSources, gSearchView, gSearchBox;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
@ -32,7 +31,7 @@ function test() {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
gDebuggee.firstCall();
callInTab(gTab, "firstCall");
});
}
@ -111,7 +110,6 @@ function testEscape() {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;

View File

@ -7,13 +7,12 @@
const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gSearchBox;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
@ -111,7 +110,6 @@ function pressKeyToHide(aKey) {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gSearchBox = null;

View File

@ -7,16 +7,15 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gSources, gSearchView, gSearchBox;
function test() {
// Debug test slaves are a bit slow at this test.
requestLongerTimeout(3);
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSources = gDebugger.DebuggerView.Sources;
@ -226,7 +225,6 @@ function verifyContents(aArgs) {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gSources = null;

View File

@ -7,16 +7,15 @@
const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gSources, gSourceUtils, gSearchView, gSearchBox;
function test() {
// Debug test slaves are a bit slow at this test.
requestLongerTimeout(3);
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSources = gDebugger.DebuggerView.Sources;
@ -268,7 +267,6 @@ function verifyContents(aMatches) {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gSources = null;

View File

@ -7,13 +7,12 @@
const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gSources, gSearchBox;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSources = gDebugger.DebuggerView.Sources;
@ -92,7 +91,6 @@ function verifySourcesPane() {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gSources = null;

View File

@ -7,13 +7,12 @@
const TAB_URL = EXAMPLE_URL + "doc_function-search.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gEditor, gSources, gSearchBox, gFilteredFunctions;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
@ -459,7 +458,6 @@ function writeInfo() {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;

View File

@ -8,13 +8,12 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gSearchBox, gSearchBoxPanel;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
@ -28,7 +27,7 @@ function test() {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
gDebuggee.firstCall();
callInTab(gTab, "firstCall");
});
}
@ -52,7 +51,6 @@ function hidePopup() {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gSearchBox = null;

View File

@ -8,13 +8,12 @@
const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gTab, gPanel, gDebugger;
let gEditor, gSearchBox, gSearchBoxPanel;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gEditor = gDebugger.DebuggerView.editor;
@ -34,7 +33,7 @@ function test() {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
gDebuggee.firstCall();
callInTab(gTab, "firstCall");
});
}
@ -77,7 +76,6 @@ function testFocusLost() {
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gEditor = null;

View File

@ -6,7 +6,7 @@
*/
function test() {
initDebugger("about:blank").then(([aTab, aDebuggee, aPanel]) => {
initDebugger("about:blank").then(([aTab,, aPanel]) => {
let filterView = aPanel.panelWin.DebuggerView.Filtering;
let searchbox = aPanel.panelWin.DebuggerView.Filtering._searchbox;

View File

@ -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
};

View File

@ -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;

View File

@ -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);
},
/**

View File

@ -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() {

View File

@ -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);

View File

@ -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();

View File

@ -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) {

View File

@ -130,9 +130,6 @@ endif
ifdef MOZ_SHARED_ICU
DEFINES += -DMOZ_SHARED_ICU
endif
ifdef MOZ_REPLACE_MALLOC
DEFINES += -DMOZ_REPLACE_MALLOC
endif
ifdef MOZ_JEMALLOC3
DEFINES += -DMOZ_JEMALLOC3
endif

View File

@ -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

View File

@ -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;">

View File

@ -103,7 +103,7 @@
.cm-s-mozilla .cm-unused-line {
text-decoration: line-through;
-moz-text-decoration-color: #5f88b0;
text-decoration-color: #5f88b0;
}
.cm-s-mozilla .cm-executed-line {

View File

@ -76,7 +76,7 @@
.cm-s-mozilla .cm-unused-line {
text-decoration: line-through;
-moz-text-decoration-color: #5f88b0;
text-decoration-color: #5f88b0;
}
.cm-s-mozilla .cm-executed-line {

View File

@ -198,7 +198,7 @@
}
.theme-light .ruleview-overridden {
-moz-text-decoration-color: #667380; /* Content (Text) - Dark Grey */
text-decoration-color: #667380; /* Content (Text) - Dark Grey */
}
.styleinspector-propertyeditor {

View File

@ -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;
}

View File

@ -7161,6 +7161,7 @@ if test -n "$MOZ_REPLACE_MALLOC" -a -z "$MOZ_MEMORY"; then
dnl deliberate choice not to enable it (bug 702250, for instance)
AC_MSG_ERROR([--enable-replace-malloc requires --enable-jemalloc])
elif test -n "$MOZ_REPLACE_MALLOC"; then
AC_DEFINE(MOZ_REPLACE_MALLOC)
MOZ_NATIVE_JEMALLOC=
dnl Replace-malloc Mac linkage quirks
@ -7383,17 +7384,7 @@ USE_ELF_HACK=1
MOZ_ARG_DISABLE_BOOL(elf-hack,
[ --disable-elf-hack Disable elf hacks],
[USE_ELF_HACK=],
[USE_ELF_HACK=F # Force enable elf-hack])
# Disable elf hack for profiling because the built in profiler
# doesn't read the segments properly with elf hack. This is
# temporary and should be fixed soon in the profiler.
if test "$MOZ_PROFILING" = 1; then
if test "$USE_ELF_HACK" = F; then
AC_ERROR([--enable-elf-hack is not compatible with --enable-profiling])
fi
USE_ELF_HACK=
fi
[USE_ELF_HACK=1])
# Only enable elfhack where supported
if test "$USE_ELF_HACK" = 1; then

View File

@ -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);
});

View File

@ -14,7 +14,7 @@
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
// Nothing.
dump("ReadyState = " + xhr.readyState + "\n");
};
xhr.open("get", theURL, true);
xhr.send();

View File

@ -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();
}

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -62,7 +62,6 @@ EventSource::EventSource(nsPIDOMWindow* aOwnerWindow) :
mGoingToDispatchAllMessages(false),
mWithCredentials(false),
mWaitingForOnStopRequest(false),
mInterrupted(false),
mLastConvertionResult(NS_OK),
mReadyState(CONNECTING),
mScriptLine(0),
@ -341,21 +340,14 @@ EventSource::OnStartRequest(nsIRequest *aRequest,
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest, &rv);
NS_ENSURE_SUCCESS(rv, rv);
bool requestSucceeded;
rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString contentType;
rv = httpChannel->GetContentType(contentType);
NS_ENSURE_SUCCESS(rv, rv);
nsresult status;
aRequest->GetStatus(&status);
rv = aRequest->GetStatus(&status);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_FAILED(status) || !requestSucceeded ||
!contentType.EqualsLiteral(TEXT_EVENT_STREAM)) {
DispatchFailConnection();
return NS_ERROR_NOT_AVAILABLE;
if (NS_FAILED(status)) {
// EventSource::OnStopRequest will evaluate if it shall either reestablish
// or fail the connection
return NS_ERROR_ABORT;
}
uint32_t httpStatus;
@ -363,7 +355,15 @@ EventSource::OnStartRequest(nsIRequest *aRequest,
NS_ENSURE_SUCCESS(rv, rv);
if (httpStatus != 200) {
mInterrupted = true;
DispatchFailConnection();
return NS_ERROR_ABORT;
}
nsAutoCString contentType;
rv = httpChannel->GetContentType(contentType);
NS_ENSURE_SUCCESS(rv, rv);
if (!contentType.EqualsLiteral(TEXT_EVENT_STREAM)) {
DispatchFailConnection();
return NS_ERROR_ABORT;
}
@ -454,19 +454,27 @@ EventSource::OnStopRequest(nsIRequest *aRequest,
return NS_ERROR_ABORT;
}
if (NS_FAILED(aStatusCode)) {
// "Network errors that prevents the connection from being established in the
// first place (e.g. DNS errors), must cause the user agent to asynchronously
// reestablish the connection.
//
// (...) the cancelation of the fetch algorithm by the user agent (e.g. in
// response to window.stop() or the user canceling the network connection
// manually) must cause the user agent to fail the connection.
if (NS_FAILED(aStatusCode) &&
aStatusCode != NS_ERROR_CONNECTION_REFUSED &&
aStatusCode != NS_ERROR_NET_TIMEOUT &&
aStatusCode != NS_ERROR_NET_RESET &&
aStatusCode != NS_ERROR_NET_INTERRUPT &&
aStatusCode != NS_ERROR_PROXY_CONNECTION_REFUSED &&
aStatusCode != NS_ERROR_DNS_LOOKUP_QUEUE_FULL) {
DispatchFailConnection();
return aStatusCode;
return NS_ERROR_ABORT;
}
nsresult rv;
nsresult healthOfRequestResult = CheckHealthOfRequestCallback(aRequest);
if (NS_SUCCEEDED(healthOfRequestResult) &&
mLastConvertionResult == NS_PARTIAL_MORE_INPUT) {
// we had an incomplete UTF8 char at the end of the stream
rv = ParseCharacter(REPLACEMENT_CHAR);
NS_ENSURE_SUCCESS(rv, rv);
}
nsresult rv = CheckHealthOfRequestCallback(aRequest);
NS_ENSURE_SUCCESS(rv, rv);
ClearFields();
@ -477,7 +485,7 @@ EventSource::OnStopRequest(nsIRequest *aRequest,
rv = NS_DispatchToMainThread(event);
NS_ENSURE_SUCCESS(rv, rv);
return healthOfRequestResult;
return NS_OK;
}
/**
@ -869,11 +877,6 @@ EventSource::ReestablishConnection()
return;
}
if (mReadyState != OPEN) {
NS_WARNING("Unexpected mReadyState!!!");
return;
}
nsresult rv = ResetConnection();
if (NS_FAILED(rv)) {
NS_WARNING("Failed to reset the connection!!!");
@ -994,7 +997,7 @@ EventSource::ConsoleError()
NS_ConvertUTF8toUTF16 specUTF16(targetSpec);
const char16_t *formatStrings[] = { specUTF16.get() };
if (mReadyState == CONNECTING && !mInterrupted) {
if (mReadyState == CONNECTING) {
rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
MOZ_UTF16("connectionFailure"),
formatStrings, ArrayLength(formatStrings));

View File

@ -221,7 +221,6 @@ protected:
bool mGoingToDispatchAllMessages;
bool mWithCredentials;
bool mWaitingForOnStopRequest;
bool mInterrupted;
// used while reading the input streams
nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder;

View File

@ -14094,8 +14094,12 @@ void
nsGlobalWindow::ClearDocumentDependentSlots(JSContext* aCx)
{
MOZ_ASSERT(IsInnerWindow());
WindowBinding::ClearCachedDocumentValue(aCx, this);
WindowBinding::ClearCachedPerformanceValue(aCx, this);
// If JSAPI OOMs here, there is basically nothing we can do to recover safely.
if (!WindowBinding::ClearCachedDocumentValue(aCx, this) ||
!WindowBinding::ClearCachedPerformanceValue(aCx, this)) {
MOZ_CRASH("Unhandlable OOM while clearing document dependent slots.");
}
}
/* static */

View File

@ -182,7 +182,7 @@ nsImageLoadingContent::Notify(imgIRequest* aRequest,
}
nsresult status =
reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
return OnStopRequest(aRequest, status);
return OnLoadComplete(aRequest, status);
}
if (aType == imgINotificationObserver::DECODE_COMPLETE) {
@ -205,8 +205,7 @@ nsImageLoadingContent::Notify(imgIRequest* aRequest,
}
nsresult
nsImageLoadingContent::OnStopRequest(imgIRequest* aRequest,
nsresult aStatus)
nsImageLoadingContent::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus)
{
uint32_t oldStatus;
aRequest->GetImageStatus(&oldStatus);

View File

@ -209,7 +209,7 @@ protected:
nsIContent* aBindingParent, bool aCompileEventHandlers);
void UnbindFromTree(bool aDeep, bool aNullParent);
nsresult OnStopRequest(imgIRequest* aRequest, nsresult aStatus);
nsresult OnLoadComplete(imgIRequest* aRequest, nsresult aStatus);
void OnUnlockedDraw();
nsresult OnImageIsAnimated(imgIRequest *aRequest);

View File

@ -2411,14 +2411,6 @@ DOMGCSliceCallback(JSRuntime *aRt, JS::GCProgress aProgress, const JS::GCDescrip
(*sPrevGCSliceCallback)(aRt, aProgress, aDesc);
}
void
nsJSContext::ReportPendingException()
{
if (mIsInitialized) {
nsJSUtils::ReportPendingException(mContext);
}
}
void
nsJSContext::SetWindowProxy(JS::Handle<JSObject*> aWindowProxy)
{

View File

@ -146,11 +146,6 @@ protected:
nsresult AddSupportsPrimitiveTojsvals(nsISupports *aArg, JS::Value *aArgv);
// Report the pending exception on our mContext, if any. This
// function will set aside the frame chain on mContext before
// reporting.
void ReportPendingException();
private:
void DestroyJSContext();

View File

@ -28,6 +28,7 @@
#include "nsHashKeys.h"
#include "nsCCUncollectableMarker.h"
#include "nsNameSpaceManager.h"
#include "nsDocument.h"
using namespace mozilla;
using mozilla::dom::NodeInfo;
@ -153,11 +154,6 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(nsNodeInfoManager)
NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsNodeInfoManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsNodeInfoManager)
if (tmp->mDocument &&
nsCCUncollectableMarker::InGeneration(cb,
tmp->mDocument->GetMarkedCCGeneration())) {
return NS_SUCCESS_INTERRUPTED_TRAVERSE;
}
if (tmp->mNonDocumentNodeInfos) {
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mDocument)
}
@ -167,6 +163,24 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsNodeInfoManager, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsNodeInfoManager, Release)
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsNodeInfoManager)
if (tmp->mDocument) {
return NS_CYCLE_COLLECTION_PARTICIPANT(nsDocument)->CanSkip(tmp->mDocument, aRemovingAllowed);
}
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsNodeInfoManager)
if (tmp->mDocument) {
return NS_CYCLE_COLLECTION_PARTICIPANT(nsDocument)->CanSkipInCC(tmp->mDocument);
}
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsNodeInfoManager)
if (tmp->mDocument) {
return NS_CYCLE_COLLECTION_PARTICIPANT(nsDocument)->CanSkipThis(tmp->mDocument);
}
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
nsresult
nsNodeInfoManager::Init(nsIDocument *aDocument)
{

View File

@ -40,7 +40,7 @@ private:
public:
nsNodeInfoManager();
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsNodeInfoManager)
NS_DECL_CYCLE_COLLECTION_SKIPPABLE_NATIVE_CLASS(nsNodeInfoManager)
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsNodeInfoManager)

View File

@ -353,13 +353,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=338583
function fnMessageListenerTest3h(e) {
fnMessageListenerTest3h.msg_ok = (fnMessageListenerTest3h.msg_ok && e.data == "ok");
fnMessageListenerTest3h.id_ok = (fnMessageListenerTest3h.msg_ok && e.lastEventId == "");
fnMessageListenerTest3h.id_ok = (fnMessageListenerTest3h.id_ok && e.lastEventId == "");
}
function doTest3_h(test_id) {
gEventSourceObj3_h = new EventSource("badMessageEvent2.eventsource");
gEventSourceObj3_h.addEventListener('message event', fnMessageListenerTest3h, true);
gEventSourceObj3_h.addEventListener('message', fnMessageListenerTest3h, true);
fnMessageListenerTest3h.msg_ok = true;
fnMessageListenerTest3h.id_ok = true;
@ -375,7 +375,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=338583
}
gEventSourceObj3_h.close();
setTestHasFinished(test_id);
}, parseInt(3000*stress_factor));
}, parseInt(6000*stress_factor));
}
// in order to test (4)

View File

@ -3474,7 +3474,6 @@ class CGClearCachedValueMethod(CGAbstractMethod):
JSAutoCompartment ac(aCx, obj);
if (!get_${name}(aCx, obj, aObject, args)) {
js::SetReservedSlot(obj, ${slotIndex}, oldValue);
nsJSUtils::ReportPendingException(aCx);
return false;
}
return true;

View File

@ -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]);

View File

@ -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;
};
},
};

View File

@ -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

View File

@ -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',
]

View File

@ -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);
};

View File

@ -4,47 +4,48 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WebGL1Context.h"
#include "mozilla/dom/WebGLRenderingContextBinding.h"
#include "mozilla/dom/WebGLRenderingContextBinding.h"
#include "mozilla/Telemetry.h"
using namespace mozilla;
namespace mozilla {
// -----------------------------------------------------------------------------
// CONSTRUCTOR & DESTRUCTOR
/*static*/ WebGL1Context*
WebGL1Context::Create()
{
return new WebGL1Context();
}
WebGL1Context::WebGL1Context()
: WebGLContext()
{
}
WebGL1Context::~WebGL1Context()
{
}
// -----------------------------------------------------------------------------
// IMPLEMENT nsWrapperCache
////////////////////////////////////////
// nsWrapperCache
JSObject*
WebGL1Context::WrapObject(JSContext *cx)
WebGL1Context::WrapObject(JSContext* cx)
{
return dom::WebGLRenderingContextBinding::Wrap(cx, this);
}
} // namespace mozilla
// -----------------------------------------------------------------------------
// INSTANCING nsIDOMWebGLRenderingContext
////////////////////////////////////////
// nsIDOMWebGLRenderingContext
nsresult
NS_NewCanvasRenderingContextWebGL(nsIDOMWebGLRenderingContext** aResult)
NS_NewCanvasRenderingContextWebGL(nsIDOMWebGLRenderingContext** out_result)
{
Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
nsIDOMWebGLRenderingContext* ctx = new WebGL1Context();
mozilla::Telemetry::Accumulate(mozilla::Telemetry::CANVAS_WEBGL_USED, 1);
NS_ADDREF(*aResult = ctx);
nsIDOMWebGLRenderingContext* ctx = mozilla::WebGL1Context::Create();
NS_ADDREF(*out_result = ctx);
return NS_OK;
}

View File

@ -3,8 +3,8 @@
* 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 WEBGL1CONTEXT_H_
#define WEBGL1CONTEXT_H_
#ifndef WEBGL_1_CONTEXT_H_
#define WEBGL_1_CONTEXT_H_
#include "WebGLContext.h"
@ -13,34 +13,23 @@ namespace mozilla {
class WebGL1Context
: public WebGLContext
{
// -----------------------------------------------------------------------------
// PUBLIC
public:
static WebGL1Context* Create();
// -------------------------------------------------------------------------
// CONSTRUCTOR & DESTRUCTOR
private:
WebGL1Context();
public:
virtual ~WebGL1Context();
// -------------------------------------------------------------------------
// IMPLEMENT WebGLContext
virtual bool IsWebGL2() const MOZ_OVERRIDE
{
virtual bool IsWebGL2() const MOZ_OVERRIDE {
return false;
}
// -------------------------------------------------------------------------
// IMPLEMENT nsWrapperCache
virtual JSObject* WrapObject(JSContext *cx) MOZ_OVERRIDE;
// nsWrapperCache
virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
};
} // namespace mozilla
#endif
#endif // WEBGL_1_CONTEXT_H_

Some files were not shown because too many files have changed in this diff Show More