--- a/accessible/interfaces/gecko/IGeckoCustom.idl
+++ b/accessible/interfaces/gecko/IGeckoCustom.idl
@@ -8,16 +8,17 @@ import "objidl.idl";
import "oaidl.idl";
[object, uuid(7510360f-cdae-4de9-88c8-d167eda62afc)]
interface IGeckoCustom : IUnknown
{
[propget] HRESULT ID([out, retval] unsigned __int64* aID);
[propget] HRESULT anchorCount([out, retval] long* aCount);
[propget] HRESULT DOMNodeID([out, retval] BSTR* aID);
+ [propget] HRESULT minimumIncrement([out, retval] double* aIncrement);
}
[
uuid(55769d85-f830-4d76-9e39-3670914a28f7),
helpstring("private custom gecko interfaces")
]
library IGeckoCustom
--- a/accessible/ipc/win/ProxyAccessible.cpp
+++ b/accessible/ipc/win/ProxyAccessible.cpp
@@ -150,16 +150,33 @@ ProxyAccessible::Value(nsString& aValue)
HRESULT hr = acc->get_accValue(kChildIdSelf, &result);
_bstr_t resultWrap(result, false);
if (FAILED(hr)) {
return;
}
aValue = (wchar_t*)resultWrap;
}
+double
+ProxyAccessible::Step()
+{
+ RefPtr<IGeckoCustom> custom = QueryInterface<IGeckoCustom>(this);
+ if (!custom) {
+ return 0;
+ }
+
+ double increment;
+ HRESULT hr = custom->get_minimumIncrement(&increment);
+ if (FAILED(hr)) {
+ return 0;
+ }
+
+ return increment;
+}
+
void
ProxyAccessible::Description(nsString& aDesc) const
{
aDesc.Truncate();
RefPtr<IAccessible> acc;
if (!GetCOMInterface((void**)getter_AddRefs(acc))) {
return;
}
--- a/accessible/windows/msaa/GeckoCustom.cpp
+++ b/accessible/windows/msaa/GeckoCustom.cpp
@@ -38,8 +38,15 @@ GeckoCustom::get_DOMNodeID(BSTR* aID)
}
STDMETHODIMP
GeckoCustom::get_ID(uint64_t* aID)
{
*aID = mAcc->IsDoc() ? 0 : reinterpret_cast<uintptr_t>(mAcc.get());
return S_OK;
}
+
+STDMETHODIMP
+GeckoCustom::get_minimumIncrement(double* aIncrement)
+{
+ *aIncrement = mAcc->Step();
+ return S_OK;
+}
--- a/accessible/windows/msaa/GeckoCustom.h
+++ b/accessible/windows/msaa/GeckoCustom.h
@@ -24,16 +24,17 @@ public:
explicit GeckoCustom(AccessibleWrap* aAcc) : mAcc(aAcc) {}
// IUnknown
DECL_IUNKNOWN
virtual STDMETHODIMP get_anchorCount(long* aCount);
virtual STDMETHODIMP get_DOMNodeID(BSTR* aID);
virtual STDMETHODIMP get_ID(uint64_t* aID);
+ virtual STDMETHODIMP get_minimumIncrement(double* aIncrement);
private:
GeckoCustom() = delete;
GeckoCustom& operator =(const GeckoCustom&) = delete;
GeckoCustom(const GeckoCustom&) = delete;
GeckoCustom(GeckoCustom&&) = delete;
GeckoCustom& operator=(GeckoCustom&&) = delete;
--- a/accessible/xpcom/xpcAccessibleValue.cpp
+++ b/accessible/xpcom/xpcAccessibleValue.cpp
@@ -114,20 +114,16 @@ xpcAccessibleValue::GetMinimumIncrement(
if (Intl().IsAccessible() && Intl().AsAccessible()->IsDefunct())
return NS_ERROR_FAILURE;
double value;
if (Intl().IsAccessible()) {
value = Intl().AsAccessible()->Step();
} else {
-#if defined(XP_WIN)
- return NS_ERROR_NOT_IMPLEMENTED;
-#else
value = Intl().AsProxy()->Step();
-#endif
}
if (!IsNaN(value))
*aValue = value;
return NS_OK;
}
--- a/browser/base/content/test/referrer/browser.ini
+++ b/browser/base/content/test/referrer/browser.ini
@@ -14,13 +14,13 @@ skip-if = os == 'linux' # Bug 1145199
[browser_referrer_open_link_in_tab.js]
skip-if = os == 'linux' # Bug 1144816
[browser_referrer_open_link_in_window.js]
skip-if = os == 'linux' # Bug 1145199
[browser_referrer_open_link_in_window_in_container.js]
skip-if = os == 'linux' # Bug 1145199
[browser_referrer_simple_click.js]
[browser_referrer_open_link_in_container_tab.js]
-skip-if = os == 'linux' || e10s # Bug 1144816, Bug 1315042
+skip-if = os == 'linux' # Bug 1144816
[browser_referrer_open_link_in_container_tab2.js]
skip-if = os == 'linux' # Bug 1144816
[browser_referrer_open_link_in_container_tab3.js]
skip-if = os == 'linux' # Bug 1144816
--- a/browser/base/content/test/tabs/browser_allow_process_switches_despite_related_browser.js
+++ b/browser/base/content/test/tabs/browser_allow_process_switches_despite_related_browser.js
@@ -1,32 +1,33 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
const DUMMY_FILE = "dummy_page.html";
+const DATA_URI = "data:text/html,Hi";
+const DATA_URI_SOURCE = "view-source:" + DATA_URI;
// Test for bug 1328829.
add_task(function* () {
- let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
- "data:text/html,Hi");
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, DATA_URI);
registerCleanupFunction(function* () {
yield BrowserTestUtils.removeTab(tab);
});
- let promiseTab =
- BrowserTestUtils.waitForNewTab(gBrowser, "view-source:data:text/html,Hi");
+ let promiseTab = BrowserTestUtils.waitForNewTab(gBrowser, DATA_URI_SOURCE);
BrowserViewSource(tab.linkedBrowser);
let viewSourceTab = yield promiseTab;
registerCleanupFunction(function* () {
yield BrowserTestUtils.removeTab(viewSourceTab);
});
-
let dummyPage = getChromeDir(getResolvedURI(gTestPath));
dummyPage.append(DUMMY_FILE);
const uriString = Services.io.newFileURI(dummyPage).spec;
let viewSourceBrowser = viewSourceTab.linkedBrowser;
+ let promiseLoad =
+ BrowserTestUtils.browserLoaded(viewSourceBrowser, false, uriString);
viewSourceBrowser.loadURI(uriString);
- let href = yield BrowserTestUtils.browserLoaded(viewSourceBrowser);
+ let href = yield promiseLoad;
is(href, uriString,
"Check file:// URI loads in a browser that was previously for view-source");
});
--- a/browser/components/extensions/.eslintrc.js
+++ b/browser/components/extensions/.eslintrc.js
@@ -1,23 +1,20 @@
"use strict";
module.exports = { // eslint-disable-line no-undef
"extends": "../../../toolkit/components/extensions/.eslintrc.js",
"globals": {
- "AllWindowEvents": true,
+ "EventEmitter": true,
+ "IconDetails": true,
+ "Tab": true,
+ "TabContext": true,
+ "Window": true,
+ "WindowEventManager": true,
"browserActionFor": true,
- "currentWindow": true,
- "EventEmitter": true,
- "getBrowserInfo": true,
"getCookieStoreIdForTab": true,
- "IconDetails": true,
"makeWidgetId": true,
"pageActionFor": true,
- "PanelPopup": true,
- "TabContext": true,
- "ViewPopup": true,
- "WindowEventManager": true,
- "WindowListManager": true,
- "WindowManager": true,
+ "tabTracker": true,
+ "windowTracker": true,
},
};
copy from browser/components/extensions/ext-utils.js
copy to browser/components/extensions/ExtensionPopups.jsm
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ExtensionPopups.jsm
@@ -1,51 +1,49 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
+/* exported PanelPopup, ViewPopup */
+
+var EXPORTED_SYMBOLS = ["BasePopup", "PanelPopup", "ViewPopup"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
"resource:///modules/E10SUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
- "resource://gre/modules/NetUtil.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
- "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionParent",
+ "resource://gre/modules/ExtensionParent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
"resource://gre/modules/Timer.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
- "@mozilla.org/content/style-sheet-service;1",
- "nsIStyleSheetService");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
-Cu.import("resource://gre/modules/ExtensionUtils.jsm");
-Cu.import("resource://gre/modules/AppConstants.jsm");
+var {
+ DefaultWeakMap,
+ promiseEvent,
+} = ExtensionUtils;
+
const POPUP_LOAD_TIMEOUT_MS = 200;
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-var {
- DefaultWeakMap,
- promiseEvent,
- SingletonEventManager,
-} = ExtensionUtils;
-
-// This file provides some useful code for the |tabs| and |windows|
-// modules. All of the code is installed on |global|, which is a scope
-// shared among the different ext-*.js scripts.
-
-global.makeWidgetId = id => {
+function makeWidgetId(id) {
id = id.toLowerCase();
// FIXME: This allows for collisions.
return id.replace(/[^a-z0-9_-]/g, "_");
-};
+}
function promisePopupShown(popup) {
return new Promise(resolve => {
if (popup.state == "open") {
resolve();
} else {
popup.addEventListener("popupshown", function(event) {
resolve();
@@ -253,17 +251,17 @@ class BasePopup {
if (this.extension.remote) {
readyPromise = promiseEvent(browser, "XULFrameLoaderCreated");
} else {
readyPromise = promiseEvent(browser, "load");
}
viewNode.appendChild(browser);
- extensions.emit("extension-browser-inserted", browser);
+ ExtensionParent.apiManager.emit("extension-browser-inserted", browser);
let setupBrowser = browser => {
let mm = browser.messageManager;
mm.addMessageListener("DOMTitleChanged", this);
mm.addMessageListener("Extension:BrowserBackgroundChanged", this);
mm.addMessageListener("Extension:BrowserContentLoaded", this);
mm.addMessageListener("Extension:BrowserResized", this);
mm.addMessageListener("Extension:DOMWindowClose", this, true);
@@ -539,758 +537,8 @@ class ViewPopup extends BasePopup {
CustomizableUI.hidePanelForNode(this.viewNode);
} else if (this.attached) {
this.destroyed = true;
} else {
this.destroy();
}
}
}
-
-Object.assign(global, {PanelPopup, ViewPopup});
-
-// Manages tab-specific context data, and dispatching tab select events
-// across all windows.
-global.TabContext = function TabContext(getDefaults, extension) {
- this.extension = extension;
- this.getDefaults = getDefaults;
-
- this.tabData = new WeakMap();
- this.lastLocation = new WeakMap();
-
- AllWindowEvents.addListener("progress", this);
- AllWindowEvents.addListener("TabSelect", this);
-
- EventEmitter.decorate(this);
-};
-
-TabContext.prototype = {
- get(tab) {
- if (!this.tabData.has(tab)) {
- this.tabData.set(tab, this.getDefaults(tab));
- }
-
- return this.tabData.get(tab);
- },
-
- clear(tab) {
- this.tabData.delete(tab);
- },
-
- handleEvent(event) {
- if (event.type == "TabSelect") {
- let tab = event.target;
- this.emit("tab-select", tab);
- this.emit("location-change", tab);
- }
- },
-
- onStateChange(browser, webProgress, request, stateFlags, statusCode) {
- let flags = Ci.nsIWebProgressListener;
-
- if (!(~stateFlags & (flags.STATE_IS_WINDOW | flags.STATE_START) ||
- this.lastLocation.has(browser))) {
- this.lastLocation.set(browser, request.URI);
- }
- },
-
- onLocationChange(browser, webProgress, request, locationURI, flags) {
- let gBrowser = browser.ownerGlobal.gBrowser;
- let lastLocation = this.lastLocation.get(browser);
- if (browser === gBrowser.selectedBrowser &&
- !(lastLocation && lastLocation.equalsExceptRef(browser.currentURI))) {
- let tab = gBrowser.getTabForBrowser(browser);
- this.emit("location-change", tab, true);
- }
- this.lastLocation.set(browser, browser.currentURI);
- },
-
- shutdown() {
- AllWindowEvents.removeListener("progress", this);
- AllWindowEvents.removeListener("TabSelect", this);
- },
-};
-
-// Manages tab mappings and permissions for a specific extension.
-function ExtensionTabManager(extension) {
- this.extension = extension;
-
- // A mapping of tab objects to the inner window ID the extension currently has
- // the active tab permission for. The active permission for a given tab is
- // valid only for the inner window that was active when the permission was
- // granted. If the tab navigates, the inner window ID changes, and the
- // permission automatically becomes stale.
- //
- // WeakMap[tab => inner-window-id<int>]
- this.hasTabPermissionFor = new WeakMap();
-}
-
-ExtensionTabManager.prototype = {
- addActiveTabPermission(tab = TabManager.activeTab) {
- if (this.extension.hasPermission("activeTab")) {
- // Note that, unlike Chrome, we don't currently clear this permission with
- // the tab navigates. If the inner window is revived from BFCache before
- // we've granted this permission to a new inner window, the extension
- // maintains its permissions for it.
- this.hasTabPermissionFor.set(tab, tab.linkedBrowser.innerWindowID);
- }
- },
-
- revokeActiveTabPermission(tab = TabManager.activeTab) {
- this.hasTabPermissionFor.delete(tab);
- },
-
- // Returns true if the extension has the "activeTab" permission for this tab.
- // This is somewhat more permissive than the generic "tabs" permission, as
- // checked by |hasTabPermission|, in that it also allows programmatic script
- // injection without an explicit host permission.
- hasActiveTabPermission(tab) {
- // This check is redundant with addTabPermission, but cheap.
- if (this.extension.hasPermission("activeTab")) {
- return (this.hasTabPermissionFor.has(tab) &&
- this.hasTabPermissionFor.get(tab) === tab.linkedBrowser.innerWindowID);
- }
- return false;
- },
-
- hasTabPermission(tab) {
- return this.extension.hasPermission("tabs") || this.hasActiveTabPermission(tab);
- },
-
- convert(tab) {
- let window = tab.ownerGlobal;
- let browser = tab.linkedBrowser;
-
- let mutedInfo = {muted: tab.muted};
- if (tab.muteReason === null) {
- mutedInfo.reason = "user";
- } else if (tab.muteReason) {
- mutedInfo.reason = "extension";
- mutedInfo.extensionId = tab.muteReason;
- }
-
- let result = {
- id: TabManager.getId(tab),
- index: tab._tPos,
- windowId: WindowManager.getId(window),
- selected: tab.selected,
- highlighted: tab.selected,
- active: tab.selected,
- pinned: tab.pinned,
- status: TabManager.getStatus(tab),
- incognito: WindowManager.isBrowserPrivate(browser),
- width: browser.frameLoader.lazyWidth || browser.clientWidth,
- height: browser.frameLoader.lazyHeight || browser.clientHeight,
- audible: tab.soundPlaying,
- mutedInfo,
- };
- if (this.extension.hasPermission("cookies")) {
- result.cookieStoreId = getCookieStoreIdForTab(result, tab);
- }
-
- if (this.hasTabPermission(tab)) {
- result.url = browser.currentURI.spec;
- let title = browser.contentTitle || tab.label;
- if (title) {
- result.title = title;
- }
- let icon = window.gBrowser.getIcon(tab);
- if (icon) {
- result.favIconUrl = icon;
- }
- }
-
- return result;
- },
-
- // Converts tabs returned from SessionStore.getClosedTabData and
- // SessionStore.getClosedWindowData into API tab objects
- convertFromSessionStoreClosedData(tab, window) {
- let result = {
- sessionId: String(tab.closedId),
- index: tab.pos ? tab.pos : 0,
- windowId: WindowManager.getId(window),
- selected: false,
- highlighted: false,
- active: false,
- pinned: false,
- incognito: Boolean(tab.state && tab.state.isPrivate),
- };
-
- if (this.hasTabPermission(tab)) {
- let entries = tab.state ? tab.state.entries : tab.entries;
- result.url = entries[0].url;
- result.title = entries[0].title;
- if (tab.image) {
- result.favIconUrl = tab.image;
- }
- }
-
- return result;
- },
-
- getTabs(window) {
- return Array.from(window.gBrowser.tabs)
- .filter(tab => !tab.closing)
- .map(tab => this.convert(tab));
- },
-};
-
-function getBrowserInfo(browser) {
- if (!browser.ownerGlobal.gBrowser) {
- // When we're loaded into a <browser> inside about:addons, we need to go up
- // one more level.
- browser = browser.ownerGlobal.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDocShell)
- .chromeEventHandler;
-
- if (!browser) {
- return {};
- }
- }
-
- let result = {};
-
- let window = browser.ownerGlobal;
- if (window.gBrowser) {
- let tab = window.gBrowser.getTabForBrowser(browser);
- if (tab) {
- result.tabId = TabManager.getId(tab);
- }
-
- result.windowId = WindowManager.getId(window);
- }
-
- return result;
-}
-global.getBrowserInfo = getBrowserInfo;
-
-// Sends the tab and windowId upon request. This is primarily used to support
-// the synchronous `browser.extension.getViews` API.
-let onGetTabAndWindowId = {
- receiveMessage({name, target, sync}) {
- let result = getBrowserInfo(target);
-
- if (result.tabId) {
- if (sync) {
- return result;
- }
- target.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", result);
- }
- },
-};
-/* eslint-disable mozilla/balanced-listeners */
-Services.mm.addMessageListener("Extension:GetTabAndWindowId", onGetTabAndWindowId);
-/* eslint-enable mozilla/balanced-listeners */
-
-
-// Manages global mappings between XUL tabs and extension tab IDs.
-global.TabManager = {
- _tabs: new WeakMap(),
- _nextId: 1,
- _initialized: false,
-
- // We begin listening for TabOpen and TabClose events once we've started
- // assigning IDs to tabs, so that we can remap the IDs of tabs which are moved
- // between windows.
- initListener() {
- if (this._initialized) {
- return;
- }
-
- AllWindowEvents.addListener("TabOpen", this);
- AllWindowEvents.addListener("TabClose", this);
- WindowListManager.addOpenListener(this.handleWindowOpen.bind(this));
-
- this._initialized = true;
- },
-
- handleEvent(event) {
- if (event.type == "TabOpen") {
- let {adoptedTab} = event.detail;
- if (adoptedTab) {
- // This tab is being created to adopt a tab from a different window.
- // Copy the ID from the old tab to the new.
- let tab = event.target;
- this._tabs.set(tab, this.getId(adoptedTab));
-
- tab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
- windowId: WindowManager.getId(tab.ownerGlobal),
- });
- }
- } else if (event.type == "TabClose") {
- let {adoptedBy} = event.detail;
- if (adoptedBy) {
- // This tab is being closed because it was adopted by a new window.
- // Copy its ID to the new tab, in case it was created as the first tab
- // of a new window, and did not have an `adoptedTab` detail when it was
- // opened.
- this._tabs.set(adoptedBy, this.getId(event.target));
-
- adoptedBy.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
- windowId: WindowManager.getId(adoptedBy),
- });
- }
- }
- },
-
- handleWindowOpen(window) {
- if (window.arguments && window.arguments[0] instanceof window.XULElement) {
- // If the first window argument is a XUL element, it means the
- // window is about to adopt a tab from another window to replace its
- // initial tab.
- let adoptedTab = window.arguments[0];
-
- this._tabs.set(window.gBrowser.tabs[0], this.getId(adoptedTab));
- }
- },
-
- getId(tab) {
- if (this._tabs.has(tab)) {
- return this._tabs.get(tab);
- }
- this.initListener();
-
- let id = this._nextId++;
- this._tabs.set(tab, id);
- return id;
- },
-
- getBrowserId(browser) {
- let gBrowser = browser.ownerGlobal.gBrowser;
- // Some non-browser windows have gBrowser but not
- // getTabForBrowser!
- if (gBrowser && gBrowser.getTabForBrowser) {
- let tab = gBrowser.getTabForBrowser(browser);
- if (tab) {
- return this.getId(tab);
- }
- }
- return -1;
- },
-
- /**
- * Returns the XUL <tab> element associated with the given tab ID. If no tab
- * with the given ID exists, and no default value is provided, an error is
- * raised, belonging to the scope of the given context.
- *
- * @param {integer} tabId
- * The ID of the tab to retrieve.
- * @param {ExtensionContext} context
- * The context of the caller.
- * This value may be omitted if `default_` is not `undefined`.
- * @param {*} default_
- * The value to return if no tab exists with the given ID.
- * @returns {Element<tab>}
- * A XUL <tab> element.
- */
- getTab(tabId, context, default_ = undefined) {
- // FIXME: Speed this up without leaking memory somehow.
- for (let window of WindowListManager.browserWindows()) {
- if (!window.gBrowser) {
- continue;
- }
- for (let tab of window.gBrowser.tabs) {
- if (this.getId(tab) == tabId) {
- return tab;
- }
- }
- }
- if (default_ !== undefined) {
- return default_;
- }
- throw new context.cloneScope.Error(`Invalid tab ID: ${tabId}`);
- },
-
- get activeTab() {
- let window = WindowManager.topWindow;
- if (window && window.gBrowser) {
- return window.gBrowser.selectedTab;
- }
- return null;
- },
-
- getStatus(tab) {
- return tab.getAttribute("busy") == "true" ? "loading" : "complete";
- },
-
- convert(extension, tab) {
- return TabManager.for(extension).convert(tab);
- },
-};
-
-// WeakMap[Extension -> ExtensionTabManager]
-let tabManagers = new WeakMap();
-
-// Returns the extension-specific tab manager for the given extension, or
-// creates one if it doesn't already exist.
-TabManager.for = function(extension) {
- if (!tabManagers.has(extension)) {
- tabManagers.set(extension, new ExtensionTabManager(extension));
- }
- return tabManagers.get(extension);
-};
-
-/* eslint-disable mozilla/balanced-listeners */
-extensions.on("shutdown", (type, extension) => {
- tabManagers.delete(extension);
-});
-/* eslint-enable mozilla/balanced-listeners */
-
-function memoize(fn) {
- let weakMap = new DefaultWeakMap(fn);
- return weakMap.get.bind(weakMap);
-}
-
-// Manages mapping between XUL windows and extension window IDs.
-global.WindowManager = {
- // Note: These must match the values in windows.json.
- WINDOW_ID_NONE: -1,
- WINDOW_ID_CURRENT: -2,
-
- get topWindow() {
- return Services.wm.getMostRecentWindow("navigator:browser");
- },
-
- windowType(window) {
- // TODO: Make this work.
-
- let {chromeFlags} = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDocShell)
- .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIXULWindow);
-
- if (chromeFlags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG) {
- return "popup";
- }
-
- return "normal";
- },
-
- updateGeometry(window, options) {
- if (options.left !== null || options.top !== null) {
- let left = options.left !== null ? options.left : window.screenX;
- let top = options.top !== null ? options.top : window.screenY;
- window.moveTo(left, top);
- }
-
- if (options.width !== null || options.height !== null) {
- let width = options.width !== null ? options.width : window.outerWidth;
- let height = options.height !== null ? options.height : window.outerHeight;
- window.resizeTo(width, height);
- }
- },
-
- isBrowserPrivate: memoize(browser => {
- return PrivateBrowsingUtils.isBrowserPrivate(browser);
- }),
-
- getId: memoize(window => {
- if (window instanceof Ci.nsIInterfaceRequestor) {
- return window.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
- }
- return null;
- }),
-
- getWindow(id, context) {
- if (id == this.WINDOW_ID_CURRENT) {
- return currentWindow(context);
- }
-
- for (let window of WindowListManager.browserWindows(true)) {
- if (this.getId(window) == id) {
- return window;
- }
- }
- return null;
- },
-
- getState(window) {
- const STATES = {
- [window.STATE_MAXIMIZED]: "maximized",
- [window.STATE_MINIMIZED]: "minimized",
- [window.STATE_NORMAL]: "normal",
- };
- let state = STATES[window.windowState];
- if (window.fullScreen) {
- state = "fullscreen";
- }
- return state;
- },
-
- setState(window, state) {
- if (state != "fullscreen" && window.fullScreen) {
- window.fullScreen = false;
- }
-
- switch (state) {
- case "maximized":
- window.maximize();
- break;
-
- case "minimized":
- case "docked":
- window.minimize();
- break;
-
- case "normal":
- // Restore sometimes returns the window to its previous state, rather
- // than to the "normal" state, so it may need to be called anywhere from
- // zero to two times.
- window.restore();
- if (window.windowState != window.STATE_NORMAL) {
- window.restore();
- }
- if (window.windowState != window.STATE_NORMAL) {
- // And on OS-X, where normal vs. maximized is basically a heuristic,
- // we need to cheat.
- window.sizeToContent();
- }
- break;
-
- case "fullscreen":
- window.fullScreen = true;
- break;
-
- default:
- throw new Error(`Unexpected window state: ${state}`);
- }
- },
-
- convert(extension, window, getInfo) {
- let xulWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDocShell)
- .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIXULWindow);
-
- let result = {
- id: this.getId(window),
- focused: window.document.hasFocus(),
- top: window.screenY,
- left: window.screenX,
- width: window.outerWidth,
- height: window.outerHeight,
- incognito: PrivateBrowsingUtils.isWindowPrivate(window),
- type: this.windowType(window),
- state: this.getState(window),
- alwaysOnTop: xulWindow.zLevel >= Ci.nsIXULWindow.raisedZ,
- };
-
- if (getInfo && getInfo.populate) {
- result.tabs = TabManager.for(extension).getTabs(window);
- }
-
- return result;
- },
-
- // Converts windows returned from SessionStore.getClosedWindowData
- // into API window objects
- convertFromSessionStoreClosedData(window, extension) {
- let result = {
- sessionId: String(window.closedId),
- focused: false,
- incognito: false,
- type: "normal", // this is always "normal" for a closed window
- state: this.getState(window),
- alwaysOnTop: false,
- };
-
- if (window.tabs.length) {
- result.tabs = [];
- window.tabs.forEach((tab, index) => {
- result.tabs.push(TabManager.for(extension).convertFromSessionStoreClosedData(tab, window, index));
- });
- }
-
- return result;
- },
-};
-
-// Manages listeners for window opening and closing. A window is
-// considered open when the "load" event fires on it. A window is
-// closed when a "domwindowclosed" notification fires for it.
-global.WindowListManager = {
- _openListeners: new Set(),
- _closeListeners: new Set(),
-
- // Returns an iterator for all browser windows. Unless |includeIncomplete| is
- // true, only fully-loaded windows are returned.
- * browserWindows(includeIncomplete = false) {
- // The window type parameter is only available once the window's document
- // element has been created. This means that, when looking for incomplete
- // browser windows, we need to ignore the type entirely for windows which
- // haven't finished loading, since we would otherwise skip browser windows
- // in their early loading stages.
- // This is particularly important given that the "domwindowcreated" event
- // fires for browser windows when they're in that in-between state, and just
- // before we register our own "domwindowcreated" listener.
-
- let e = Services.wm.getEnumerator("");
- while (e.hasMoreElements()) {
- let window = e.getNext();
-
- let ok = includeIncomplete;
- if (window.document.readyState == "complete") {
- ok = window.document.documentElement.getAttribute("windowtype") == "navigator:browser";
- }
-
- if (ok) {
- yield window;
- }
- }
- },
-
- addOpenListener(listener) {
- if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
- Services.ww.registerNotification(this);
- }
- this._openListeners.add(listener);
-
- for (let window of this.browserWindows(true)) {
- if (window.document.readyState != "complete") {
- window.addEventListener("load", this);
- }
- }
- },
-
- removeOpenListener(listener) {
- this._openListeners.delete(listener);
- if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
- Services.ww.unregisterNotification(this);
- }
- },
-
- addCloseListener(listener) {
- if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
- Services.ww.registerNotification(this);
- }
- this._closeListeners.add(listener);
- },
-
- removeCloseListener(listener) {
- this._closeListeners.delete(listener);
- if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
- Services.ww.unregisterNotification(this);
- }
- },
-
- handleEvent(event) {
- event.currentTarget.removeEventListener(event.type, this);
- let window = event.target.defaultView;
- if (window.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
- return;
- }
-
- for (let listener of this._openListeners) {
- listener(window);
- }
- },
-
- observe(window, topic, data) {
- if (topic == "domwindowclosed") {
- if (window.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
- return;
- }
-
- window.removeEventListener("load", this);
- for (let listener of this._closeListeners) {
- listener(window);
- }
- } else {
- window.addEventListener("load", this);
- }
- },
-};
-
-// Provides a facility to listen for DOM events across all XUL windows.
-global.AllWindowEvents = {
- _listeners: new Map(),
-
- // If |type| is a normal event type, invoke |listener| each time
- // that event fires in any open window. If |type| is "progress", add
- // a web progress listener that covers all open windows.
- addListener(type, listener) {
- if (type == "domwindowopened") {
- return WindowListManager.addOpenListener(listener);
- } else if (type == "domwindowclosed") {
- return WindowListManager.addCloseListener(listener);
- }
-
- if (this._listeners.size == 0) {
- WindowListManager.addOpenListener(this.openListener);
- }
-
- if (!this._listeners.has(type)) {
- this._listeners.set(type, new Set());
- }
- let list = this._listeners.get(type);
- list.add(listener);
-
- // Register listener on all existing windows.
- for (let window of WindowListManager.browserWindows()) {
- this.addWindowListener(window, type, listener);
- }
- },
-
- removeListener(eventType, listener) {
- if (eventType == "domwindowopened") {
- return WindowListManager.removeOpenListener(listener);
- } else if (eventType == "domwindowclosed") {
- return WindowListManager.removeCloseListener(listener);
- }
-
- let listeners = this._listeners.get(eventType);
- listeners.delete(listener);
- if (listeners.size == 0) {
- this._listeners.delete(eventType);
- if (this._listeners.size == 0) {
- WindowListManager.removeOpenListener(this.openListener);
- }
- }
-
- // Unregister listener from all existing windows.
- let useCapture = eventType === "focus" || eventType === "blur";
- for (let window of WindowListManager.browserWindows()) {
- if (eventType == "progress") {
- window.gBrowser.removeTabsProgressListener(listener);
- } else {
- window.removeEventListener(eventType, listener, useCapture);
- }
- }
- },
-
- /* eslint-disable mozilla/balanced-listeners */
- addWindowListener(window, eventType, listener) {
- let useCapture = eventType === "focus" || eventType === "blur";
-
- if (eventType == "progress") {
- window.gBrowser.addTabsProgressListener(listener);
- } else {
- window.addEventListener(eventType, listener, useCapture);
- }
- },
- /* eslint-enable mozilla/balanced-listeners */
-
- // Runs whenever the "load" event fires for a new window.
- openListener(window) {
- for (let [eventType, listeners] of AllWindowEvents._listeners) {
- for (let listener of listeners) {
- this.addWindowListener(window, eventType, listener);
- }
- }
- },
-};
-
-AllWindowEvents.openListener = AllWindowEvents.openListener.bind(AllWindowEvents);
-
-// Subclass of SingletonEventManager where we just need to call
-// add/removeEventListener on each XUL window.
-global.WindowEventManager = class extends SingletonEventManager {
- constructor(context, name, event, listener) {
- super(context, name, fire => {
- let listener2 = (...args) => listener(fire, ...args);
- AllWindowEvents.addListener(event, listener2);
- return () => {
- AllWindowEvents.removeListener(event, listener2);
- };
- });
- }
-};
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -3,28 +3,30 @@
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
"resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
"resource://gre/modules/Timer.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ViewPopup",
+ "resource:///modules/ExtensionPopups.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
"@mozilla.org/inspector/dom-utils;1",
"inIDOMUtils");
Cu.import("resource://devtools/shared/event-emitter.js");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
Cu.import("resource://gre/modules/Task.jsm");
var {
+ IconDetails,
SingletonEventManager,
- IconDetails,
} = ExtensionUtils;
const POPUP_PRELOAD_TIMEOUT_MS = 200;
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
function isAncestorOrSelf(target, node) {
for (; node; node = node.parentNode) {
@@ -46,17 +48,17 @@ function BrowserAction(options, extensio
let widgetId = makeWidgetId(extension.id);
this.id = `${widgetId}-browser-action`;
this.viewId = `PanelUI-webext-${widgetId}-browser-action-view`;
this.widget = null;
this.pendingPopup = null;
this.pendingPopupTimeout = null;
- this.tabManager = TabManager.for(extension);
+ this.tabManager = extension.tabManager;
this.defaults = {
enabled: true,
title: options.default_title || extension.name,
badgeText: "",
badgeBackgroundColor: null,
icon: IconDetails.normalize({path: options.default_icon}, extension),
popup: options.default_popup || "",
@@ -381,17 +383,17 @@ BrowserAction.prototype = {
// title, badge, etc. If it only changes a parameter for a single
// tab, |tab| will be that tab. Otherwise it will be null.
updateOnChange(tab) {
if (tab) {
if (tab.selected) {
this.updateWindow(tab.ownerGlobal);
}
} else {
- for (let window of WindowListManager.browserWindows()) {
+ for (let window of windowTracker.browserWindows()) {
this.updateWindow(window);
}
}
},
// tab is allowed to be null.
// prop should be one of "icon", "title", "badgeText", "popup", or "badgeBackgroundColor".
setProperty(tab, prop, value) {
@@ -439,107 +441,116 @@ extensions.on("shutdown", (type, extensi
browserActionMap.get(extension).shutdown();
browserActionMap.delete(extension);
}
});
/* eslint-enable mozilla/balanced-listeners */
extensions.registerSchemaAPI("browserAction", "addon_parent", context => {
let {extension} = context;
+
+ let {tabManager} = extension;
+
+ function getTab(tabId) {
+ if (tabId !== null) {
+ return tabTracker.getTab(tabId);
+ }
+ return null;
+ }
+
return {
browserAction: {
onClicked: new SingletonEventManager(context, "browserAction.onClicked", fire => {
let listener = () => {
- let tab = TabManager.activeTab;
- fire.async(TabManager.convert(extension, tab));
+ fire.async(tabManager.convert(tabTracker.activeTab));
};
BrowserAction.for(extension).on("click", listener);
return () => {
BrowserAction.for(extension).off("click", listener);
};
}).api(),
enable: function(tabId) {
- let tab = tabId !== null ? TabManager.getTab(tabId, context) : null;
+ let tab = getTab(tabId);
BrowserAction.for(extension).setProperty(tab, "enabled", true);
},
disable: function(tabId) {
- let tab = tabId !== null ? TabManager.getTab(tabId, context) : null;
+ let tab = getTab(tabId);
BrowserAction.for(extension).setProperty(tab, "enabled", false);
},
setTitle: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+ let tab = getTab(details.tabId);
let title = details.title;
// Clear the tab-specific title when given a null string.
if (tab && title == "") {
title = null;
}
BrowserAction.for(extension).setProperty(tab, "title", title);
},
getTitle: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+ let tab = getTab(details.tabId);
let title = BrowserAction.for(extension).getProperty(tab, "title");
return Promise.resolve(title);
},
setIcon: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+ let tab = getTab(details.tabId);
let icon = IconDetails.normalize(details, extension, context);
BrowserAction.for(extension).setProperty(tab, "icon", icon);
},
setBadgeText: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+ let tab = getTab(details.tabId);
BrowserAction.for(extension).setProperty(tab, "badgeText", details.text);
},
getBadgeText: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+ let tab = getTab(details.tabId);
let text = BrowserAction.for(extension).getProperty(tab, "badgeText");
return Promise.resolve(text);
},
setPopup: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+ let tab = getTab(details.tabId);
// Note: Chrome resolves arguments to setIcon relative to the calling
// context, but resolves arguments to setPopup relative to the extension
// root.
// For internal consistency, we currently resolve both relative to the
// calling context.
let url = details.popup && context.uri.resolve(details.popup);
BrowserAction.for(extension).setProperty(tab, "popup", url);
},
getPopup: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+ let tab = getTab(details.tabId);
let popup = BrowserAction.for(extension).getProperty(tab, "popup");
return Promise.resolve(popup);
},
setBadgeBackgroundColor: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+ let tab = getTab(details.tabId);
let color = details.color;
if (!Array.isArray(color)) {
let col = DOMUtils.colorToRGBA(color);
color = col && [col.r, col.g, col.b, Math.round(col.a * 255)];
}
BrowserAction.for(extension).setProperty(tab, "badgeBackgroundColor", color);
},
getBadgeBackgroundColor: function(details, callback) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
+ let tab = getTab(details.tabId);
let color = BrowserAction.for(extension).getProperty(tab, "badgeBackgroundColor");
return Promise.resolve(color || [0xd9, 0, 0, 255]);
},
},
};
});
--- a/browser/components/extensions/ext-commands.js
+++ b/browser/components/extensions/ext-commands.js
@@ -31,41 +31,41 @@ function CommandList(manifest, extension
}
CommandList.prototype = {
/**
* Registers the commands to all open windows and to any which
* are later created.
*/
register() {
- for (let window of WindowListManager.browserWindows()) {
+ for (let window of windowTracker.browserWindows()) {
this.registerKeysToDocument(window);
}
this.windowOpenListener = (window) => {
if (!this.keysetsMap.has(window)) {
this.registerKeysToDocument(window);
}
};
- WindowListManager.addOpenListener(this.windowOpenListener);
+ windowTracker.addOpenListener(this.windowOpenListener);
},
/**
* Unregisters the commands from all open windows and stops commands
* from being registered to windows which are later created.
*/
unregister() {
- for (let window of WindowListManager.browserWindows()) {
+ for (let window of windowTracker.browserWindows()) {
if (this.keysetsMap.has(window)) {
this.keysetsMap.get(window).remove();
}
}
- WindowListManager.removeOpenListener(this.windowOpenListener);
+ windowTracker.removeOpenListener(this.windowOpenListener);
},
/**
* Creates a Map from commands for each command in the manifest.commands object.
*
* @param {Object} manifest The manifest JSON object.
* @returns {Map<string, object>}
*/
@@ -127,18 +127,18 @@ CommandList.prototype = {
keyElement.addEventListener("command", (event) => {
if (name == "_execute_page_action") {
let win = event.target.ownerGlobal;
pageActionFor(this.extension).triggerAction(win);
} else if (name == "_execute_browser_action") {
let win = event.target.ownerGlobal;
browserActionFor(this.extension).triggerAction(win);
} else {
- TabManager.for(this.extension)
- .addActiveTabPermission(TabManager.activeTab);
+ this.extension.tabManager
+ .addActiveTabPermission();
this.emit("command", name);
}
});
/* eslint-enable mozilla/balanced-listeners */
return keyElement;
},
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -85,17 +85,17 @@ var gMenuBuilder = {
this.itemsToCleanUp.add(rootElement);
}
},
// Builds a context menu for browserAction and pageAction buttons.
buildActionContextMenu(contextData) {
const {menu} = contextData;
- contextData.tab = TabManager.activeTab;
+ contextData.tab = tabTracker.activeTab;
contextData.pageUrl = contextData.tab.linkedBrowser.currentURI.spec;
const root = gRootItems.get(contextData.extension);
const children = this.buildChildren(root, contextData);
const visible = children.slice(0, ACTION_MENU_TOP_LEVEL_LIMIT);
if (visible.length) {
this.xulMenu = menu;
@@ -323,17 +323,17 @@ function getContexts(contextData) {
return contexts;
}
function MenuItem(extension, createProperties, isRoot = false) {
this.extension = extension;
this.children = [];
this.parent = null;
- this.tabManager = TabManager.for(extension);
+ this.tabManager = extension.tabManager;
this.setDefaults();
this.setProps(createProperties);
if (!this.hasOwnProperty("_id")) {
this.id = gNextMenuItemID++;
}
// If the item is not the root and has no parent
@@ -541,46 +541,46 @@ MenuItem.prototype = {
},
};
// While any extensions are active, this Tracker registers to observe/listen
// for contex-menu events from both content and chrome.
const contextMenuTracker = {
register() {
Services.obs.addObserver(this, "on-build-contextmenu", false);
- for (const window of WindowListManager.browserWindows()) {
+ for (const window of windowTracker.browserWindows()) {
this.onWindowOpen(window);
}
- WindowListManager.addOpenListener(this.onWindowOpen);
+ windowTracker.addOpenListener(this.onWindowOpen);
},
unregister() {
Services.obs.removeObserver(this, "on-build-contextmenu");
- for (const window of WindowListManager.browserWindows()) {
+ for (const window of windowTracker.browserWindows()) {
const menu = window.document.getElementById("tabContextMenu");
menu.removeEventListener("popupshowing", this);
}
- WindowListManager.removeOpenListener(this.onWindowOpen);
+ windowTracker.removeOpenListener(this.onWindowOpen);
},
observe(subject, topic, data) {
subject = subject.wrappedJSObject;
gMenuBuilder.build(subject);
},
onWindowOpen(window) {
const menu = window.document.getElementById("tabContextMenu");
menu.addEventListener("popupshowing", contextMenuTracker);
},
handleEvent(event) {
const menu = event.target;
if (menu.id === "tabContextMenu") {
const trigger = menu.triggerNode;
- const tab = trigger.localName === "tab" ? trigger : TabManager.activeTab;
+ const tab = trigger.localName === "tab" ? trigger : tabTracker.activeTab;
const pageUrl = tab.linkedBrowser.currentURI.spec;
gMenuBuilder.build({menu, tab, pageUrl, onTab: true});
}
},
};
var gExtensionCount = 0;
/* eslint-disable mozilla/balanced-listeners */
--- a/browser/components/extensions/ext-desktop-runtime.js
+++ b/browser/components/extensions/ext-desktop-runtime.js
@@ -1,20 +1,20 @@
"use strict";
/* eslint-disable mozilla/balanced-listeners */
extensions.on("uninstall", (msg, extension) => {
if (extension.uninstallURL) {
- let browser = WindowManager.topWindow.gBrowser;
+ let browser = windowTracker.topWindow.gBrowser;
browser.addTab(extension.uninstallURL, {relatedToCurrent: true});
}
});
global.openOptionsPage = (extension) => {
- let window = WindowManager.topWindow;
+ let window = windowTracker.topWindow;
if (!window) {
return Promise.reject({message: "No browser window available"});
}
if (extension.manifest.options_ui.open_in_tab) {
window.switchToTabHavingURI(extension.manifest.options_ui.page, true);
return Promise.resolve();
}
--- a/browser/components/extensions/ext-devtools.js
+++ b/browser/components/extensions/ext-devtools.js
@@ -76,17 +76,17 @@ global.getTargetTabIdForToolbox = (toolb
if (!target.isLocalTab) {
throw new Error("Unexpected target type: only local tabs are currently supported.");
}
let parentWindow = target.tab.linkedBrowser.ownerGlobal;
let tab = parentWindow.gBrowser.getTabForBrowser(target.tab.linkedBrowser);
- return TabManager.getId(tab);
+ return tabTracker.getId(tab);
};
/**
* The DevToolsPage represents the "devtools_page" related to a particular
* Toolbox and WebExtension.
*
* The devtools_page contexts are invisible WebExtensions contexts, similar to the
* background page, associated to a single developer toolbox (e.g. If an add-on
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -1,29 +1,32 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
+XPCOMUtils.defineLazyModuleGetter(this, "PanelPopup",
+ "resource:///modules/ExtensionPopups.jsm");
+
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
SingletonEventManager,
IconDetails,
} = ExtensionUtils;
// WeakMap[Extension -> PageAction]
var pageActionMap = new WeakMap();
// Handles URL bar icons, including the |page_action| manifest entry
// and associated API.
function PageAction(options, extension) {
this.extension = extension;
this.id = makeWidgetId(extension.id) + "-page-action";
- this.tabManager = TabManager.for(extension);
+ this.tabManager = extension.tabManager;
this.defaults = {
show: false,
title: options.default_title || extension.name,
icon: IconDetails.normalize({path: options.default_icon}, extension),
popup: options.default_popup || "",
};
@@ -206,17 +209,17 @@ PageAction.prototype = {
this.tabContext.clear(tab);
}
this.updateButton(tab.ownerGlobal);
},
shutdown() {
this.tabContext.shutdown();
- for (let window of WindowListManager.browserWindows()) {
+ for (let window of windowTracker.browserWindows()) {
if (this.buttons.has(window)) {
this.buttons.get(window).remove();
window.removeEventListener("popupshowing", this);
}
}
},
};
@@ -237,74 +240,77 @@ extensions.on("shutdown", (type, extensi
PageAction.for = extension => {
return pageActionMap.get(extension);
};
global.pageActionFor = PageAction.for;
extensions.registerSchemaAPI("pageAction", "addon_parent", context => {
let {extension} = context;
+
+ const {tabManager} = extension;
+
return {
pageAction: {
onClicked: new SingletonEventManager(context, "pageAction.onClicked", fire => {
let listener = (evt, tab) => {
- fire.async(TabManager.convert(extension, tab));
+ fire.async(tabManager.convert(tab));
};
let pageAction = PageAction.for(extension);
pageAction.on("click", listener);
return () => {
pageAction.off("click", listener);
};
}).api(),
show(tabId) {
- let tab = TabManager.getTab(tabId, context);
+ let tab = tabTracker.getTab(tabId);
PageAction.for(extension).setProperty(tab, "show", true);
},
hide(tabId) {
- let tab = TabManager.getTab(tabId, context);
+ let tab = tabTracker.getTab(tabId);
PageAction.for(extension).setProperty(tab, "show", false);
},
setTitle(details) {
- let tab = TabManager.getTab(details.tabId, context);
+ let tab = tabTracker.getTab(details.tabId);
// Clear the tab-specific title when given a null string.
PageAction.for(extension).setProperty(tab, "title", details.title || null);
},
getTitle(details) {
- let tab = TabManager.getTab(details.tabId, context);
+ let tab = tabTracker.getTab(details.tabId);
let title = PageAction.for(extension).getProperty(tab, "title");
return Promise.resolve(title);
},
setIcon(details) {
- let tab = TabManager.getTab(details.tabId, context);
+ let tab = tabTracker.getTab(details.tabId);
let icon = IconDetails.normalize(details, extension, context);
PageAction.for(extension).setProperty(tab, "icon", icon);
},
setPopup(details) {
- let tab = TabManager.getTab(details.tabId, context);
+ let tab = tabTracker.getTab(details.tabId);
// Note: Chrome resolves arguments to setIcon relative to the calling
// context, but resolves arguments to setPopup relative to the extension
// root.
// For internal consistency, we currently resolve both relative to the
// calling context.
let url = details.popup && context.uri.resolve(details.popup);
PageAction.for(extension).setProperty(tab, "popup", url);
},
getPopup(details) {
- let tab = TabManager.getTab(details.tabId, context);
+ let tab = tabTracker.getTab(details.tabId);
let popup = PageAction.for(extension).getProperty(tab, "popup");
return Promise.resolve(popup);
},
},
};
});
--- a/browser/components/extensions/ext-sessions.js
+++ b/browser/components/extensions/ext-sessions.js
@@ -16,46 +16,46 @@ const SS_ON_CLOSED_OBJECTS_CHANGED = "se
function getRecentlyClosed(maxResults, extension) {
let recentlyClosed = [];
// Get closed windows
let closedWindowData = SessionStore.getClosedWindowData(false);
for (let window of closedWindowData) {
recentlyClosed.push({
lastModified: window.closedAt,
- window: WindowManager.convertFromSessionStoreClosedData(window, extension)});
+ window: Window.convertFromSessionStoreClosedData(extension, window)});
}
// Get closed tabs
- for (let window of WindowListManager.browserWindows()) {
+ for (let window of windowTracker.browserWindows()) {
let closedTabData = SessionStore.getClosedTabData(window, false);
for (let tab of closedTabData) {
recentlyClosed.push({
lastModified: tab.closedAt,
- tab: TabManager.for(extension).convertFromSessionStoreClosedData(tab, window)});
+ tab: Tab.convertFromSessionStoreClosedData(extension, tab, window)});
}
}
// Sort windows and tabs
recentlyClosed.sort((a, b) => b.lastModified - a.lastModified);
return recentlyClosed.slice(0, maxResults);
}
function createSession(restored, extension, sessionId) {
if (!restored) {
return Promise.reject({message: `Could not restore object using sessionId ${sessionId}.`});
}
let sessionObj = {lastModified: Date.now()};
if (restored instanceof Ci.nsIDOMChromeWindow) {
return promiseObserved("sessionstore-single-window-restored", subject => subject == restored).then(() => {
- sessionObj.window = WindowManager.convert(extension, restored, {populate: true});
+ sessionObj.window = extension.windowManager.convert(restored, {populate: true});
return Promise.resolve([sessionObj]);
});
}
- sessionObj.tab = TabManager.for(extension).convert(restored);
+ sessionObj.tab = extension.tabManager.convert(restored);
return Promise.resolve([sessionObj]);
}
extensions.registerSchemaAPI("sessions", "addon_parent", context => {
let {extension} = context;
return {
sessions: {
getRecentlyClosed: function(filter) {
@@ -70,17 +70,17 @@ extensions.registerSchemaAPI("sessions",
session = SessionStore.undoCloseById(closedId);
} else if (SessionStore.lastClosedObjectType == "window") {
// If the most recently closed object is a window, just undo closing the most recent window.
session = SessionStore.undoCloseWindow(0);
} else {
// It is a tab, and we cannot call SessionStore.undoCloseTab without a window,
// so we must find the tab in which case we can just use its closedId.
let recentlyClosedTabs = [];
- for (let window of WindowListManager.browserWindows()) {
+ for (let window of windowTracker.browserWindows()) {
let closedTabData = SessionStore.getClosedTabData(window, false);
for (let tab of closedTabData) {
recentlyClosedTabs.push(tab);
}
}
// Sort the tabs.
recentlyClosedTabs.sort((a, b) => b.closedAt - a.closedAt);
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -28,32 +28,31 @@ function getSender(extension, target, se
let tabId;
if ("tabId" in sender) {
// The message came from a privileged extension page running in a tab. In
// that case, it should include a tabId property (which is filled in by the
// page-open listener below).
tabId = sender.tabId;
delete sender.tabId;
} else if (target instanceof Ci.nsIDOMXULElement) {
- tabId = getBrowserInfo(target).tabId;
+ tabId = tabTracker.getBrowserData(target).tabId;
}
if (tabId) {
- let tab = TabManager.getTab(tabId, null, null);
+ let tab = extension.tabManager.get(tabId, null);
if (tab) {
- sender.tab = TabManager.convert(extension, tab);
+ sender.tab = tab.convert();
}
}
}
// Used by Extension.jsm
global.tabGetSender = getSender;
/* eslint-disable mozilla/balanced-listeners */
-
extensions.on("page-shutdown", (type, context) => {
if (context.viewType == "tab") {
if (context.extension.id !== context.xulBrowser.contentPrincipal.addonId) {
// Only close extension tabs.
// This check prevents about:addons from closing when it contains a
// WebExtension as an embedded inline options page.
return;
}
@@ -61,181 +60,34 @@ extensions.on("page-shutdown", (type, co
if (gBrowser) {
let tab = gBrowser.getTabForBrowser(context.xulBrowser);
if (tab) {
gBrowser.removeTab(tab);
}
}
}
});
-
-extensions.on("fill-browser-data", (type, browser, data) => {
- let tabId, windowId;
- if (browser) {
- ({tabId, windowId} = getBrowserInfo(browser));
- }
-
- data.tabId = tabId || -1;
- data.windowId = windowId || -1;
-});
/* eslint-enable mozilla/balanced-listeners */
-global.currentWindow = function(context) {
- let {xulWindow} = context;
- if (xulWindow && context.viewType != "background") {
- return xulWindow;
- }
- return WindowManager.topWindow;
-};
-
let tabListener = {
- init() {
- if (this.initialized) {
- return;
- }
-
- this.adoptedTabs = new WeakMap();
-
- this.handleWindowOpen = this.handleWindowOpen.bind(this);
- this.handleWindowClose = this.handleWindowClose.bind(this);
-
- AllWindowEvents.addListener("TabClose", this);
- AllWindowEvents.addListener("TabOpen", this);
- WindowListManager.addOpenListener(this.handleWindowOpen);
- WindowListManager.addCloseListener(this.handleWindowClose);
-
- EventEmitter.decorate(this);
-
- this.initialized = true;
- },
-
- handleEvent(event) {
- switch (event.type) {
- case "TabOpen":
- if (event.detail.adoptedTab) {
- this.adoptedTabs.set(event.detail.adoptedTab, event.target);
- }
-
- // We need to delay sending this event until the next tick, since the
- // tab does not have its final index when the TabOpen event is dispatched.
- Promise.resolve().then(() => {
- if (event.detail.adoptedTab) {
- this.emitAttached(event.originalTarget);
- } else {
- this.emitCreated(event.originalTarget);
- }
- });
- break;
-
- case "TabClose":
- let tab = event.originalTarget;
-
- if (event.detail.adoptedBy) {
- this.emitDetached(tab, event.detail.adoptedBy);
- } else {
- this.emitRemoved(tab, false);
- }
- break;
- }
- },
-
- handleWindowOpen(window) {
- if (window.arguments && window.arguments[0] instanceof window.XULElement) {
- // If the first window argument is a XUL element, it means the
- // window is about to adopt a tab from another window to replace its
- // initial tab.
- //
- // Note that this event handler depends on running before the
- // delayed startup code in browser.js, which is currently triggered
- // by the first MozAfterPaint event. That code handles finally
- // adopting the tab, and clears it from the arguments list in the
- // process, so if we run later than it, we're too late.
- let tab = window.arguments[0];
- this.adoptedTabs.set(tab, window.gBrowser.tabs[0]);
-
- // We need to be sure to fire this event after the onDetached event
- // for the original tab.
- let listener = (event, details) => {
- if (details.tab == tab) {
- this.off("tab-detached", listener);
-
- Promise.resolve().then(() => {
- this.emitAttached(details.adoptedBy);
- });
- }
- };
-
- this.on("tab-detached", listener);
- } else {
- for (let tab of window.gBrowser.tabs) {
- this.emitCreated(tab);
- }
- }
- },
-
- handleWindowClose(window) {
- for (let tab of window.gBrowser.tabs) {
- if (this.adoptedTabs.has(tab)) {
- this.emitDetached(tab, this.adoptedTabs.get(tab));
- } else {
- this.emitRemoved(tab, true);
- }
- }
- },
-
- emitAttached(tab) {
- let newWindowId = WindowManager.getId(tab.ownerGlobal);
- let tabId = TabManager.getId(tab);
-
- this.emit("tab-attached", {tab, tabId, newWindowId, newPosition: tab._tPos});
- },
-
- emitDetached(tab, adoptedBy) {
- let oldWindowId = WindowManager.getId(tab.ownerGlobal);
- let tabId = TabManager.getId(tab);
-
- this.emit("tab-detached", {tab, adoptedBy, tabId, oldWindowId, oldPosition: tab._tPos});
- },
-
- emitCreated(tab) {
- this.emit("tab-created", {tab});
- },
-
- emitRemoved(tab, isWindowClosing) {
- let windowId = WindowManager.getId(tab.ownerGlobal);
- let tabId = TabManager.getId(tab);
-
- // When addons run in-process, `window.close()` is synchronous. Most other
- // addon-invoked calls are asynchronous since they go through a proxy
- // context via the message manager. This includes event registrations such
- // as `tabs.onRemoved.addListener`.
- // So, even if `window.close()` were to be called (in-process) after calling
- // `tabs.onRemoved.addListener`, then the tab would be closed before the
- // event listener is registered. To make sure that the event listener is
- // notified, we dispatch `tabs.onRemoved` asynchronously.
- Services.tm.mainThread.dispatch(() => {
- this.emit("tab-removed", {tab, tabId, windowId, isWindowClosing});
- }, Ci.nsIThread.DISPATCH_NORMAL);
- },
-
tabReadyInitialized: false,
tabReadyPromises: new WeakMap(),
initializingTabs: new WeakSet(),
initTabReady() {
if (!this.tabReadyInitialized) {
- AllWindowEvents.addListener("progress", this);
+ windowTracker.addListener("progress", this);
this.tabReadyInitialized = true;
}
},
onLocationChange(browser, webProgress, request, locationURI, flags) {
if (webProgress.isTopLevel) {
- let gBrowser = browser.ownerGlobal.gBrowser;
+ let {gBrowser} = browser.ownerGlobal;
let tab = gBrowser.getTabForBrowser(browser);
// Now we are certain that the first page in the tab was loaded.
this.initializingTabs.delete(tab);
// browser.innerWindowID is now set, resolve the promises if any.
let deferred = this.tabReadyPromises.get(tab);
if (deferred) {
@@ -253,98 +105,116 @@ let tabListener = {
*
* @param {XULElement} tab The <tab> element.
* @returns {Promise} Resolves with the given tab once ready.
*/
awaitTabReady(tab) {
let deferred = this.tabReadyPromises.get(tab);
if (!deferred) {
deferred = PromiseUtils.defer();
- if (!this.initializingTabs.has(tab) && tab.linkedBrowser.innerWindowID) {
+ if (!this.initializingTabs.has(tab) && (tab.linkedBrowser.innerWindowID ||
+ tab.linkedBrowser.currentURI.spec === "about:blank")) {
deferred.resolve(tab);
} else {
this.initTabReady();
this.tabReadyPromises.set(tab, deferred);
}
}
return deferred.promise;
},
};
-/* eslint-disable mozilla/balanced-listeners */
-extensions.on("startup", () => {
- tabListener.init();
-});
-/* eslint-enable mozilla/balanced-listeners */
-
extensions.registerSchemaAPI("tabs", "addon_parent", context => {
let {extension} = context;
+
+ let {tabManager} = extension;
+
+ function getTabOrActive(tabId) {
+ if (tabId !== null) {
+ return tabTracker.getTab(tabId);
+ }
+ return tabTracker.activeTab;
+ }
+
+ async function promiseTabWhenReady(tabId) {
+ let tab;
+ if (tabId !== null) {
+ tab = tabManager.get(tabId);
+ } else {
+ tab = tabManager.getWrapper(tabTracker.activeTab);
+ }
+
+ await tabListener.awaitTabReady(tab.tab);
+
+ return tab;
+ }
+
let self = {
tabs: {
onActivated: new WindowEventManager(context, "tabs.onActivated", "TabSelect", (fire, event) => {
let tab = event.originalTarget;
- let tabId = TabManager.getId(tab);
- let windowId = WindowManager.getId(tab.ownerGlobal);
+ let tabId = tabTracker.getId(tab);
+ let windowId = windowTracker.getId(tab.ownerGlobal);
fire.async({tabId, windowId});
}).api(),
onCreated: new SingletonEventManager(context, "tabs.onCreated", fire => {
let listener = (eventName, event) => {
- fire.async(TabManager.convert(extension, event.tab));
+ fire.async(tabManager.convert(event.tab));
};
- tabListener.on("tab-created", listener);
+ tabTracker.on("tab-created", listener);
return () => {
- tabListener.off("tab-created", listener);
+ tabTracker.off("tab-created", listener);
};
}).api(),
/**
* Since multiple tabs currently can't be highlighted, onHighlighted
* essentially acts an alias for self.tabs.onActivated but returns
* the tabId in an array to match the API.
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
*/
onHighlighted: new WindowEventManager(context, "tabs.onHighlighted", "TabSelect", (fire, event) => {
let tab = event.originalTarget;
- let tabIds = [TabManager.getId(tab)];
- let windowId = WindowManager.getId(tab.ownerGlobal);
+ let tabIds = [tabTracker.getId(tab)];
+ let windowId = windowTracker.getId(tab.ownerGlobal);
fire.async({tabIds, windowId});
}).api(),
onAttached: new SingletonEventManager(context, "tabs.onAttached", fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
};
- tabListener.on("tab-attached", listener);
+ tabTracker.on("tab-attached", listener);
return () => {
- tabListener.off("tab-attached", listener);
+ tabTracker.off("tab-attached", listener);
};
}).api(),
onDetached: new SingletonEventManager(context, "tabs.onDetached", fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
};
- tabListener.on("tab-detached", listener);
+ tabTracker.on("tab-detached", listener);
return () => {
- tabListener.off("tab-detached", listener);
+ tabTracker.off("tab-detached", listener);
};
}).api(),
onRemoved: new SingletonEventManager(context, "tabs.onRemoved", fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
};
- tabListener.on("tab-removed", listener);
+ tabTracker.on("tab-removed", listener);
return () => {
- tabListener.off("tab-removed", listener);
+ tabTracker.off("tab-removed", listener);
};
}).api(),
onReplaced: ignoreEvent(context, "tabs.onReplaced"),
onMoved: new SingletonEventManager(context, "tabs.onMoved", fire => {
// There are certain circumstances where we need to ignore a move event.
//
@@ -368,28 +238,28 @@ extensions.registerSchemaAPI("tabs", "ad
let moveListener = event => {
let tab = event.originalTarget;
if (ignoreNextMove.has(tab)) {
ignoreNextMove.delete(tab);
return;
}
- fire.async(TabManager.getId(tab), {
- windowId: WindowManager.getId(tab.ownerGlobal),
+ fire.async(tabTracker.getId(tab), {
+ windowId: windowTracker.getId(tab.ownerGlobal),
fromIndex: event.detail,
toIndex: tab._tPos,
});
};
- AllWindowEvents.addListener("TabMove", moveListener);
- AllWindowEvents.addListener("TabOpen", openListener);
+ windowTracker.addListener("TabMove", moveListener);
+ windowTracker.addListener("TabOpen", openListener);
return () => {
- AllWindowEvents.removeListener("TabMove", moveListener);
- AllWindowEvents.removeListener("TabOpen", openListener);
+ windowTracker.removeListener("TabMove", moveListener);
+ windowTracker.removeListener("TabOpen", openListener);
};
}).api(),
onUpdated: new SingletonEventManager(context, "tabs.onUpdated", fire => {
const restricted = ["url", "favIconUrl", "title"];
function sanitize(extension, changeInfo) {
let result = {};
@@ -398,24 +268,20 @@ extensions.registerSchemaAPI("tabs", "ad
if (extension.hasPermission("tabs") || !restricted.includes(prop)) {
nonempty = true;
result[prop] = changeInfo[prop];
}
}
return [nonempty, result];
}
- let fireForBrowser = (browser, changed) => {
+ let fireForTab = (tab, changed) => {
let [needed, changeInfo] = sanitize(extension, changed);
if (needed) {
- let gBrowser = browser.ownerGlobal.gBrowser;
- let tabElem = gBrowser.getTabForBrowser(browser);
-
- let tab = TabManager.convert(extension, tabElem);
- fire.async(tab.id, changeInfo, tab);
+ fire.async(tab.id, changeInfo, tab.convert());
}
};
let listener = event => {
let needed = [];
if (event.type == "TabAttrModified") {
let changed = event.detail.changed;
if (changed.includes("image")) {
@@ -431,81 +297,57 @@ extensions.registerSchemaAPI("tabs", "ad
needed.push("title");
}
} else if (event.type == "TabPinned") {
needed.push("pinned");
} else if (event.type == "TabUnpinned") {
needed.push("pinned");
}
- if (needed.length && !extension.hasPermission("tabs")) {
- needed = needed.filter(attr => !restricted.includes(attr));
+ let tab = tabManager.getWrapper(event.originalTarget);
+ let changeInfo = {};
+ for (let prop of needed) {
+ changeInfo[prop] = tab[prop];
}
- if (needed.length) {
- let tab = TabManager.convert(extension, event.originalTarget);
+ fireForTab(tab, changeInfo);
+ };
- let changeInfo = {};
- for (let prop of needed) {
- changeInfo[prop] = tab[prop];
- }
- fire.async(tab.id, changeInfo, tab);
- }
- };
- let progressListener = {
- onStateChange(browser, webProgress, request, stateFlags, statusCode) {
- if (!webProgress.isTopLevel) {
- return;
+ let statusListener = ({browser, status, url}) => {
+ let {gBrowser} = browser.ownerGlobal;
+ let tabElem = gBrowser.getTabForBrowser(browser);
+ if (tabElem) {
+ let changed = {status};
+ if (url) {
+ changed.url = url;
}
- let status;
- if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
- if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
- status = "loading";
- } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
- status = "complete";
- }
- } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
- statusCode == Cr.NS_BINDING_ABORTED) {
- status = "complete";
- }
-
- fireForBrowser(browser, {status});
- },
-
- onLocationChange(browser, webProgress, request, locationURI, flags) {
- if (!webProgress.isTopLevel) {
- return;
- }
-
- fireForBrowser(browser, {
- status: webProgress.isLoadingDocument ? "loading" : "complete",
- url: locationURI.spec,
- });
- },
+ fireForTab(tabManager.wrapTab(tabElem), changed);
+ }
};
- AllWindowEvents.addListener("progress", progressListener);
- AllWindowEvents.addListener("TabAttrModified", listener);
- AllWindowEvents.addListener("TabPinned", listener);
- AllWindowEvents.addListener("TabUnpinned", listener);
+ windowTracker.addListener("status", statusListener);
+ windowTracker.addListener("TabAttrModified", listener);
+ windowTracker.addListener("TabPinned", listener);
+ windowTracker.addListener("TabUnpinned", listener);
return () => {
- AllWindowEvents.removeListener("progress", progressListener);
- AllWindowEvents.removeListener("TabAttrModified", listener);
- AllWindowEvents.removeListener("TabPinned", listener);
- AllWindowEvents.removeListener("TabUnpinned", listener);
+ windowTracker.removeListener("status", statusListener);
+ windowTracker.removeListener("TabAttrModified", listener);
+ windowTracker.removeListener("TabPinned", listener);
+ windowTracker.removeListener("TabUnpinned", listener);
};
}).api(),
- create: function(createProperties) {
+ create(createProperties) {
return new Promise((resolve, reject) => {
let window = createProperties.windowId !== null ?
- WindowManager.getWindow(createProperties.windowId, context) :
- WindowManager.topWindow;
+ windowTracker.getWindow(createProperties.windowId, context) :
+ windowTracker.topWindow;
+
if (!window.gBrowser) {
let obs = (finishedWindow, topic, data) => {
if (finishedWindow != window) {
return;
}
Services.obs.removeObserver(obs, "browser-delayed-startup-finished");
resolve(window);
};
@@ -531,21 +373,21 @@ extensions.registerSchemaAPI("tabs", "ad
let options = {};
if (createProperties.cookieStoreId) {
if (!global.isValidCookieStoreId(createProperties.cookieStoreId)) {
return Promise.reject({message: `Illegal cookieStoreId: ${createProperties.cookieStoreId}`});
}
let privateWindow = PrivateBrowsingUtils.isBrowserPrivate(window.gBrowser);
if (privateWindow && !global.isPrivateCookieStoreId(createProperties.cookieStoreId)) {
- return Promise.reject({message: `Illegal to set non-private cookieStorageId in a private window`});
+ return Promise.reject({message: `Illegal to set non-private cookieStoreId in a private window`});
}
if (!privateWindow && global.isPrivateCookieStoreId(createProperties.cookieStoreId)) {
- return Promise.reject({message: `Illegal to set private cookieStorageId in a non-private window`});
+ return Promise.reject({message: `Illegal to set private cookieStoreId in a non-private window`});
}
if (global.isContainerCookieStoreId(createProperties.cookieStoreId)) {
let containerId = global.getContainerForCookieStoreId(createProperties.cookieStoreId);
if (!containerId) {
return Promise.reject({message: `No cookie store exists with ID ${createProperties.cookieStoreId}`});
}
@@ -571,47 +413,45 @@ extensions.registerSchemaAPI("tabs", "ad
if (createProperties.index !== null) {
window.gBrowser.moveTabTo(tab, createProperties.index);
}
if (createProperties.pinned) {
window.gBrowser.pinTab(tab);
}
- if (createProperties.url && !createProperties.url.startsWith("about:")) {
+ if (createProperties.url && createProperties.url !== window.BROWSER_NEW_TAB_URL) {
// We can't wait for a location change event for about:newtab,
// since it may be pre-rendered, in which case its initial
// location change event has already fired.
// Mark the tab as initializing, so that operations like
// `executeScript` wait until the requested URL is loaded in
// the tab before dispatching messages to the inner window
// that contains the URL we're attempting to load.
tabListener.initializingTabs.add(tab);
}
- return TabManager.convert(extension, tab);
+ return tabManager.convert(tab);
});
},
- remove: function(tabs) {
+ async remove(tabs) {
if (!Array.isArray(tabs)) {
tabs = [tabs];
}
for (let tabId of tabs) {
- let tab = TabManager.getTab(tabId, context);
+ let tab = tabTracker.getTab(tabId);
tab.ownerGlobal.gBrowser.removeTab(tab);
}
-
- return Promise.resolve();
},
- update: function(tabId, updateProperties) {
- let tab = tabId !== null ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+ async update(tabId, updateProperties) {
+ let tab = getTabOrActive(tabId);
let tabbrowser = tab.ownerGlobal.gBrowser;
if (updateProperties.url !== null) {
let url = context.uri.resolve(updateProperties.url);
if (!context.checkLoadURL(url, {dontReportErrors: true})) {
return Promise.reject({message: `Illegal URL: ${url}`});
@@ -636,134 +476,65 @@ extensions.registerSchemaAPI("tabs", "ad
if (updateProperties.pinned) {
tabbrowser.pinTab(tab);
} else {
tabbrowser.unpinTab(tab);
}
}
// FIXME: highlighted/selected, openerTabId
- return Promise.resolve(TabManager.convert(extension, tab));
+ return tabManager.convert(tab);
},
- reload: function(tabId, reloadProperties) {
- let tab = tabId !== null ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+ async reload(tabId, reloadProperties) {
+ let tab = getTabOrActive(tabId);
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
if (reloadProperties && reloadProperties.bypassCache) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
}
tab.linkedBrowser.reloadWithFlags(flags);
-
- return Promise.resolve();
},
- get: function(tabId) {
- let tab = TabManager.getTab(tabId, context);
+ async get(tabId) {
+ let tab = tabTracker.getTab(tabId);
- return Promise.resolve(TabManager.convert(extension, tab));
+ return tabManager.convert(tab);
},
getCurrent() {
let tab;
if (context.tabId) {
- tab = TabManager.convert(extension, TabManager.getTab(context.tabId, context));
+ tab = tabManager.get(context.tabId).convert();
}
return Promise.resolve(tab);
},
- query: function(queryInfo) {
- let pattern = null;
+ async query(queryInfo) {
if (queryInfo.url !== null) {
if (!extension.hasPermission("tabs")) {
return Promise.reject({message: 'The "tabs" permission is required to use the query API with the "url" parameter'});
}
- pattern = new MatchPattern(queryInfo.url);
- }
-
- function matches(tab) {
- let props = ["active", "pinned", "highlighted", "status", "title", "index"];
- for (let prop of props) {
- if (queryInfo[prop] !== null && queryInfo[prop] != tab[prop]) {
- return false;
- }
- }
-
- if (queryInfo.audible !== null) {
- if (queryInfo.audible != tab.audible) {
- return false;
- }
- }
-
- if (queryInfo.muted !== null) {
- if (queryInfo.muted != tab.mutedInfo.muted) {
- return false;
- }
- }
-
- if (queryInfo.cookieStoreId !== null &&
- tab.cookieStoreId != queryInfo.cookieStoreId) {
- return false;
- }
-
- if (pattern && !pattern.matches(Services.io.newURI(tab.url))) {
- return false;
- }
-
- return true;
+ queryInfo = Object.assign({}, queryInfo);
+ queryInfo.url = new MatchPattern(queryInfo.url);
}
- let result = [];
- for (let window of WindowListManager.browserWindows()) {
- let lastFocused = window === WindowManager.topWindow;
- if (queryInfo.lastFocusedWindow !== null && queryInfo.lastFocusedWindow !== lastFocused) {
- continue;
- }
-
- let windowType = WindowManager.windowType(window);
- if (queryInfo.windowType !== null && queryInfo.windowType !== windowType) {
- continue;
- }
-
- if (queryInfo.windowId !== null) {
- if (queryInfo.windowId === WindowManager.WINDOW_ID_CURRENT) {
- if (currentWindow(context) !== window) {
- continue;
- }
- } else if (queryInfo.windowId !== WindowManager.getId(window)) {
- continue;
- }
- }
-
- if (queryInfo.currentWindow !== null) {
- let eq = window === currentWindow(context);
- if (queryInfo.currentWindow != eq) {
- continue;
- }
- }
-
- let tabs = TabManager.for(extension).getTabs(window);
- for (let tab of tabs) {
- if (matches(tab)) {
- result.push(tab);
- }
- }
- }
- return Promise.resolve(result);
+ return Array.from(tabManager.query(queryInfo, context),
+ tab => tab.convert());
},
- captureVisibleTab: function(windowId, options) {
+ captureVisibleTab(windowId, options) {
if (!extension.hasPermission("<all_urls>")) {
return Promise.reject({message: "The <all_urls> permission is required to use the captureVisibleTab API"});
}
let window = windowId == null ?
- WindowManager.topWindow :
- WindowManager.getWindow(windowId, context);
+ windowTracker.topWindow :
+ windowTracker.getWindow(windowId, context);
let tab = window.gBrowser.selectedTab;
return tabListener.awaitTabReady(tab).then(() => {
let browser = tab.linkedBrowser;
let recipient = {
innerWindowID: browser.innerWindowID,
};
@@ -783,133 +554,72 @@ extensions.registerSchemaAPI("tabs", "ad
height: browser.clientHeight,
};
return context.sendMessage(browser.messageManager, "Extension:Capture",
message, {recipient});
});
},
- detectLanguage: function(tabId) {
- let tab = tabId !== null ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+ async detectLanguage(tabId) {
+ let tab = getTabOrActive(tabId);
return tabListener.awaitTabReady(tab).then(() => {
let browser = tab.linkedBrowser;
let recipient = {innerWindowID: browser.innerWindowID};
return context.sendMessage(browser.messageManager, "Extension:DetectLanguage",
{}, {recipient});
});
},
- // Used to executeScript, insertCSS and removeCSS.
- _execute: function(tabId, details, kind, method) {
- let tab = tabId !== null ? TabManager.getTab(tabId, context) : TabManager.activeTab;
-
- let options = {
- js: [],
- css: [],
- remove_css: method == "removeCSS",
- };
-
- // We require a `code` or a `file` property, but we can't accept both.
- if ((details.code === null) == (details.file === null)) {
- return Promise.reject({message: `${method} requires either a 'code' or a 'file' property, but not both`});
- }
-
- if (details.frameId !== null && details.allFrames) {
- return Promise.reject({message: `'frameId' and 'allFrames' are mutually exclusive`});
- }
-
- if (TabManager.for(extension).hasActiveTabPermission(tab)) {
- // If we have the "activeTab" permission for this tab, ignore
- // the host whitelist.
- options.matchesHost = ["<all_urls>"];
- } else {
- options.matchesHost = extension.whiteListedHosts.serialize();
- }
+ async executeScript(tabId, details) {
+ let tab = await promiseTabWhenReady(tabId);
- if (details.code !== null) {
- options[kind + "Code"] = details.code;
- }
- if (details.file !== null) {
- let url = context.uri.resolve(details.file);
- if (!extension.isExtensionURL(url)) {
- return Promise.reject({message: "Files to be injected must be within the extension"});
- }
- options[kind].push(url);
- }
- if (details.allFrames) {
- options.all_frames = details.allFrames;
- }
- if (details.frameId !== null) {
- options.frame_id = details.frameId;
- }
- if (details.matchAboutBlank) {
- options.match_about_blank = details.matchAboutBlank;
- }
- if (details.runAt !== null) {
- options.run_at = details.runAt;
- } else {
- options.run_at = "document_idle";
- }
- if (details.cssOrigin !== null) {
- options.css_origin = details.cssOrigin;
- } else {
- options.css_origin = "author";
- }
-
- return tabListener.awaitTabReady(tab).then(() => {
- let browser = tab.linkedBrowser;
- let recipient = {
- innerWindowID: browser.innerWindowID,
- };
-
- return context.sendMessage(browser.messageManager, "Extension:Execute", {options}, {recipient});
- });
+ return tab.executeScript(context, details);
},
- executeScript: function(tabId, details) {
- return self.tabs._execute(tabId, details, "js", "executeScript");
+ async insertCSS(tabId, details) {
+ let tab = await promiseTabWhenReady(tabId);
+
+ return tab.insertCSS(context, details);
},
- insertCSS: function(tabId, details) {
- return self.tabs._execute(tabId, details, "css", "insertCSS").then(() => {});
+ async removeCSS(tabId, details) {
+ let tab = await promiseTabWhenReady(tabId);
+
+ return tab.removeCSS(context, details);
},
- removeCSS: function(tabId, details) {
- return self.tabs._execute(tabId, details, "css", "removeCSS").then(() => {});
- },
-
- move: function(tabIds, moveProperties) {
+ async move(tabIds, moveProperties) {
let index = moveProperties.index;
let tabsMoved = [];
if (!Array.isArray(tabIds)) {
tabIds = [tabIds];
}
let destinationWindow = null;
if (moveProperties.windowId !== null) {
- destinationWindow = WindowManager.getWindow(moveProperties.windowId, context);
+ destinationWindow = windowTracker.getWindow(moveProperties.windowId);
// Fail on an invalid window.
if (!destinationWindow) {
return Promise.reject({message: `Invalid window ID: ${moveProperties.windowId}`});
}
}
/*
Indexes are maintained on a per window basis so that a call to
move([tabA, tabB], {index: 0})
-> tabA to 0, tabB to 1 if tabA and tabB are in the same window
move([tabA, tabB], {index: 0})
-> tabA to 0, tabB to 0 if tabA and tabB are in different windows
*/
let indexMap = new Map();
- let tabs = tabIds.map(tabId => TabManager.getTab(tabId, context));
+ let tabs = tabIds.map(tabId => tabTracker.getTab(tabId));
for (let tab of tabs) {
// If the window is not specified, use the window from the tab.
let window = destinationWindow || tab.ownerGlobal;
let gBrowser = window.gBrowser;
let insertionPoint = indexMap.get(window) || index;
// If the index is -1 it should go to the end of the tabs.
if (insertionPoint == -1) {
@@ -934,58 +644,60 @@ extensions.registerSchemaAPI("tabs", "ad
tab = gBrowser.adoptTab(tab, insertionPoint, false);
} else {
// If the window we are moving is the same, just move the tab.
gBrowser.moveTabTo(tab, insertionPoint);
}
tabsMoved.push(tab);
}
- return Promise.resolve(tabsMoved.map(tab => TabManager.convert(extension, tab)));
+ return tabsMoved.map(tab => tabManager.convert(tab));
},
- duplicate: function(tabId) {
- let tab = TabManager.getTab(tabId, context);
+ duplicate(tabId) {
+ let tab = tabTracker.getTab(tabId);
let gBrowser = tab.ownerGlobal.gBrowser;
let newTab = gBrowser.duplicateTab(tab);
return new Promise(resolve => {
// We need to use SSTabRestoring because any attributes set before
// are ignored. SSTabRestored is too late and results in a jump in
// the UI. See http://bit.ly/session-store-api for more information.
newTab.addEventListener("SSTabRestoring", function() {
// As the tab is restoring, move it to the correct position.
+
// Pinned tabs that are duplicated are inserted
// after the existing pinned tab and pinned.
if (tab.pinned) {
gBrowser.pinTab(newTab);
}
gBrowser.moveTabTo(newTab, tab._tPos + 1);
}, {once: true});
newTab.addEventListener("SSTabRestored", function() {
// Once it has been restored, select it and return the promise.
gBrowser.selectedTab = newTab;
- return resolve(TabManager.convert(extension, newTab));
+
+ resolve(tabManager.convert(newTab));
}, {once: true});
});
},
getZoom(tabId) {
- let tab = tabId ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+ let tab = getTabOrActive(tabId);
let {ZoomManager} = tab.ownerGlobal;
let zoom = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
return Promise.resolve(zoom);
},
setZoom(tabId, zoom) {
- let tab = tabId ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+ let tab = getTabOrActive(tabId);
let {FullZoom, ZoomManager} = tab.ownerGlobal;
if (zoom === 0) {
// A value of zero means use the default zoom factor.
return FullZoom.reset(tab.linkedBrowser);
} else if (zoom >= ZoomManager.MIN && zoom <= ZoomManager.MAX) {
FullZoom.setZoom(zoom, tab.linkedBrowser);
@@ -994,33 +706,33 @@ extensions.registerSchemaAPI("tabs", "ad
message: `Zoom value ${zoom} out of range (must be between ${ZoomManager.MIN} and ${ZoomManager.MAX})`,
});
}
return Promise.resolve();
},
_getZoomSettings(tabId) {
- let tab = tabId ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+ let tab = getTabOrActive(tabId);
let {FullZoom} = tab.ownerGlobal;
return {
mode: "automatic",
scope: FullZoom.siteSpecific ? "per-origin" : "per-tab",
defaultZoomFactor: 1,
};
},
getZoomSettings(tabId) {
return Promise.resolve(this._getZoomSettings(tabId));
},
setZoomSettings(tabId, settings) {
- let tab = tabId ? TabManager.getTab(tabId, context) : TabManager.activeTab;
+ let tab = getTabOrActive(tabId);
let currentSettings = this._getZoomSettings(tab.id);
if (!Object.keys(settings).every(key => settings[key] === currentSettings[key])) {
return Promise.reject(`Unsupported zoom settings: ${JSON.stringify(settings)}`);
}
return Promise.resolve();
},
@@ -1032,17 +744,17 @@ extensions.registerSchemaAPI("tabs", "ad
return ZoomManager.getZoomForBrowser(browser);
};
// Stores the last known zoom level for each tab's browser.
// WeakMap[<browser> -> number]
let zoomLevels = new WeakMap();
// Store the zoom level for all existing tabs.
- for (let window of WindowListManager.browserWindows()) {
+ for (let window of windowTracker.browserWindows()) {
for (let tab of window.gBrowser.tabs) {
let browser = tab.linkedBrowser;
zoomLevels.set(browser, getZoomLevel(browser));
}
}
let tabCreated = (eventName, event) => {
let browser = event.tab.linkedBrowser;
@@ -1069,35 +781,35 @@ extensions.registerSchemaAPI("tabs", "ad
}
let oldZoomFactor = zoomLevels.get(browser);
let newZoomFactor = getZoomLevel(browser);
if (oldZoomFactor != newZoomFactor) {
zoomLevels.set(browser, newZoomFactor);
- let tabId = TabManager.getId(tab);
+ let tabId = tabTracker.getId(tab);
fire.async({
tabId,
oldZoomFactor,
newZoomFactor,
zoomSettings: self.tabs._getZoomSettings(tabId),
});
}
};
- tabListener.on("tab-attached", tabCreated);
- tabListener.on("tab-created", tabCreated);
+ tabTracker.on("tab-attached", tabCreated);
+ tabTracker.on("tab-created", tabCreated);
- AllWindowEvents.addListener("FullZoomChange", zoomListener);
- AllWindowEvents.addListener("TextZoomChange", zoomListener);
+ windowTracker.addListener("FullZoomChange", zoomListener);
+ windowTracker.addListener("TextZoomChange", zoomListener);
return () => {
- tabListener.off("tab-attached", tabCreated);
- tabListener.off("tab-created", tabCreated);
+ tabTracker.off("tab-attached", tabCreated);
+ tabTracker.off("tab-created", tabCreated);
- AllWindowEvents.removeListener("FullZoomChange", zoomListener);
- AllWindowEvents.removeListener("TextZoomChange", zoomListener);
+ windowTracker.removeListener("FullZoomChange", zoomListener);
+ windowTracker.removeListener("TextZoomChange", zoomListener);
};
}).api(),
},
};
return self;
});
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -1,568 +1,54 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
-XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
- "resource:///modules/CustomizableUI.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
- "resource:///modules/E10SUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
- "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
- "resource://gre/modules/Task.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
- "resource://gre/modules/Timer.jsm");
+
+/* globals TabBase, WindowBase, TabTrackerBase, WindowTrackerBase, TabManagerBase, WindowManagerBase */
+Cu.import("resource://gre/modules/ExtensionTabs.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
"@mozilla.org/content/style-sheet-service;1",
"nsIStyleSheetService");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
-Cu.import("resource://gre/modules/AppConstants.jsm");
-
-const POPUP_LOAD_TIMEOUT_MS = 200;
-
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var {
- DefaultWeakMap,
- promiseEvent,
+ ExtensionError,
SingletonEventManager,
+ defineLazyGetter,
} = ExtensionUtils;
+let tabTracker;
+let windowTracker;
+
// This file provides some useful code for the |tabs| and |windows|
// modules. All of the code is installed on |global|, which is a scope
// shared among the different ext-*.js scripts.
global.makeWidgetId = id => {
id = id.toLowerCase();
// FIXME: This allows for collisions.
return id.replace(/[^a-z0-9_-]/g, "_");
};
-function promisePopupShown(popup) {
- return new Promise(resolve => {
- if (popup.state == "open") {
- resolve();
- } else {
- popup.addEventListener("popupshown", function(event) {
- resolve();
- }, {once: true});
- }
- });
-}
-
-XPCOMUtils.defineLazyGetter(this, "popupStylesheets", () => {
- let stylesheets = ["chrome://browser/content/extension.css"];
-
- if (AppConstants.platform === "macosx") {
- stylesheets.push("chrome://browser/content/extension-mac.css");
- }
- return stylesheets;
-});
-
-XPCOMUtils.defineLazyGetter(this, "standaloneStylesheets", () => {
- let stylesheets = [];
-
- if (AppConstants.platform === "macosx") {
- stylesheets.push("chrome://browser/content/extension-mac-panel.css");
- }
- if (AppConstants.platform === "win") {
- stylesheets.push("chrome://browser/content/extension-win-panel.css");
- }
- return stylesheets;
-});
-
-class BasePopup {
- constructor(extension, viewNode, popupURL, browserStyle, fixedWidth = false) {
- this.extension = extension;
- this.popupURL = popupURL;
- this.viewNode = viewNode;
- this.browserStyle = browserStyle;
- this.window = viewNode.ownerGlobal;
- this.destroyed = false;
- this.fixedWidth = fixedWidth;
-
- extension.callOnClose(this);
-
- this.contentReady = new Promise(resolve => {
- this._resolveContentReady = resolve;
- });
-
- this.viewNode.addEventListener(this.DESTROY_EVENT, this);
-
- let doc = viewNode.ownerDocument;
- let arrowContent = doc.getAnonymousElementByAttribute(this.panel, "class", "panel-arrowcontent");
- this.borderColor = doc.defaultView.getComputedStyle(arrowContent).borderTopColor;
-
- this.browser = null;
- this.browserLoaded = new Promise((resolve, reject) => {
- this.browserLoadedDeferred = {resolve, reject};
- });
- this.browserReady = this.createBrowser(viewNode, popupURL);
-
- BasePopup.instances.get(this.window).set(extension, this);
- }
-
- static for(extension, window) {
- return BasePopup.instances.get(window).get(extension);
- }
-
- close() {
- this.closePopup();
- }
-
- destroy() {
- this.extension.forgetOnClose(this);
-
- this.destroyed = true;
- this.browserLoadedDeferred.reject(new Error("Popup destroyed"));
- return this.browserReady.then(() => {
- this.destroyBrowser(this.browser, true);
- this.browser.remove();
-
- if (this.viewNode) {
- this.viewNode.removeEventListener(this.DESTROY_EVENT, this);
- this.viewNode.style.maxHeight = "";
- }
-
- if (this.panel) {
- this.panel.style.removeProperty("--arrowpanel-background");
- this.panel.style.removeProperty("--panel-arrow-image-vertical");
- }
-
- BasePopup.instances.get(this.window).delete(this.extension);
-
- this.browser = null;
- this.viewNode = null;
- });
- }
-
- destroyBrowser(browser, finalize = false) {
- let mm = browser.messageManager;
- // If the browser has already been removed from the document, because the
- // popup was closed externally, there will be no message manager here, so
- // just replace our receiveMessage method with a stub.
- if (mm) {
- mm.removeMessageListener("DOMTitleChanged", this);
- mm.removeMessageListener("Extension:BrowserBackgroundChanged", this);
- mm.removeMessageListener("Extension:BrowserContentLoaded", this);
- mm.removeMessageListener("Extension:BrowserResized", this);
- mm.removeMessageListener("Extension:DOMWindowClose", this);
- } else if (finalize) {
- this.receiveMessage = () => {};
- }
- }
-
- // Returns the name of the event fired on `viewNode` when the popup is being
- // destroyed. This must be implemented by every subclass.
- get DESTROY_EVENT() {
- throw new Error("Not implemented");
- }
-
- get STYLESHEETS() {
- let sheets = [];
-
- if (this.browserStyle) {
- sheets.push(...popupStylesheets);
- }
- if (!this.fixedWidth) {
- sheets.push(...standaloneStylesheets);
- }
-
- return sheets;
- }
-
- get panel() {
- let panel = this.viewNode;
- while (panel && panel.localName != "panel") {
- panel = panel.parentNode;
- }
- return panel;
- }
-
- receiveMessage({name, data}) {
- switch (name) {
- case "DOMTitleChanged":
- this.viewNode.setAttribute("aria-label", this.browser.contentTitle);
- break;
-
- case "Extension:BrowserBackgroundChanged":
- this.setBackground(data.background);
- break;
-
- case "Extension:BrowserContentLoaded":
- this.browserLoadedDeferred.resolve();
- break;
-
- case "Extension:BrowserResized":
- this._resolveContentReady();
- if (this.ignoreResizes) {
- this.dimensions = data;
- } else {
- this.resizeBrowser(data);
- }
- break;
-
- case "Extension:DOMWindowClose":
- this.closePopup();
- break;
- }
- }
-
- handleEvent(event) {
- switch (event.type) {
- case this.DESTROY_EVENT:
- if (!this.destroyed) {
- this.destroy();
- }
- break;
- }
- }
-
- createBrowser(viewNode, popupURL = null) {
- let document = viewNode.ownerDocument;
- let browser = document.createElementNS(XUL_NS, "browser");
- browser.setAttribute("type", "content");
- browser.setAttribute("disableglobalhistory", "true");
- browser.setAttribute("transparent", "true");
- browser.setAttribute("class", "webextension-popup-browser");
- browser.setAttribute("webextension-view-type", "popup");
- browser.setAttribute("tooltip", "aHTMLTooltip");
-
- if (this.extension.remote) {
- browser.setAttribute("remote", "true");
- browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE);
- }
-
- // We only need flex sizing for the sake of the slide-in sub-views of the
- // main menu panel, so that the browser occupies the full width of the view,
- // and also takes up any extra height that's available to it.
- browser.setAttribute("flex", "1");
-
- // Note: When using noautohide panels, the popup manager will add width and
- // height attributes to the panel, breaking our resize code, if the browser
- // starts out smaller than 30px by 10px. This isn't an issue now, but it
- // will be if and when we popup debugging.
-
- this.browser = browser;
-
- let readyPromise;
- if (this.extension.remote) {
- readyPromise = promiseEvent(browser, "XULFrameLoaderCreated");
- } else {
- readyPromise = promiseEvent(browser, "load");
- }
-
- viewNode.appendChild(browser);
-
- extensions.emit("extension-browser-inserted", browser);
-
- let setupBrowser = browser => {
- let mm = browser.messageManager;
- mm.addMessageListener("DOMTitleChanged", this);
- mm.addMessageListener("Extension:BrowserBackgroundChanged", this);
- mm.addMessageListener("Extension:BrowserContentLoaded", this);
- mm.addMessageListener("Extension:BrowserResized", this);
- mm.addMessageListener("Extension:DOMWindowClose", this, true);
- return browser;
- };
-
- if (!popupURL) {
- // For remote browsers, we can't do any setup until the frame loader is
- // created. Non-remote browsers get a message manager immediately, so
- // there's no need to wait for the load event.
- if (this.extension.remote) {
- return readyPromise.then(() => setupBrowser(browser));
- }
- return setupBrowser(browser);
- }
-
- return readyPromise.then(() => {
- setupBrowser(browser);
- let mm = browser.messageManager;
-
- mm.loadFrameScript(
- "chrome://extensions/content/ext-browser-content.js", false);
-
- mm.sendAsyncMessage("Extension:InitBrowser", {
- allowScriptsToClose: true,
- fixedWidth: this.fixedWidth,
- maxWidth: 800,
- maxHeight: 600,
- stylesheets: this.STYLESHEETS,
- });
-
- browser.loadURI(popupURL);
- });
- }
-
- resizeBrowser({width, height, detail}) {
- if (this.fixedWidth) {
- // Figure out how much extra space we have on the side of the panel
- // opposite the arrow.
- let side = this.panel.getAttribute("side") == "top" ? "bottom" : "top";
- let maxHeight = this.viewHeight + this.extraHeight[side];
-
- height = Math.min(height, maxHeight);
- this.browser.style.height = `${height}px`;
-
- // Set a maximum height on the <panelview> element to our preferred
- // maximum height, so that the PanelUI resizing code can make an accurate
- // calculation. If we don't do this, the flex sizing logic will prevent us
- // from ever reporting a preferred size smaller than the height currently
- // available to us in the panel.
- height = Math.max(height, this.viewHeight);
- this.viewNode.style.maxHeight = `${height}px`;
- } else {
- this.browser.style.width = `${width}px`;
- this.browser.style.minWidth = `${width}px`;
- this.browser.style.height = `${height}px`;
- this.browser.style.minHeight = `${height}px`;
- }
-
- let event = new this.window.CustomEvent("WebExtPopupResized", {detail});
- this.browser.dispatchEvent(event);
- }
-
- setBackground(background) {
- let panelBackground = "";
- let panelArrow = "";
-
- if (background) {
- let borderColor = this.borderColor || background;
-
- panelBackground = background;
- panelArrow = `url("data:image/svg+xml,${encodeURIComponent(`<?xml version="1.0" encoding="UTF-8"?>
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="10">
- <path d="M 0,10 L 10,0 20,10 z" fill="${borderColor}"/>
- <path d="M 1,10 L 10,1 19,10 z" fill="${background}"/>
- </svg>
- `)}")`;
- }
-
- this.panel.style.setProperty("--arrowpanel-background", panelBackground);
- this.panel.style.setProperty("--panel-arrow-image-vertical", panelArrow);
- this.background = background;
- }
-}
-
-/**
- * A map of active popups for a given browser window.
- *
- * WeakMap[window -> WeakMap[Extension -> BasePopup]]
- */
-BasePopup.instances = new DefaultWeakMap(() => new WeakMap());
-
-class PanelPopup extends BasePopup {
- constructor(extension, imageNode, popupURL, browserStyle) {
- let document = imageNode.ownerDocument;
-
- let panel = document.createElement("panel");
- panel.setAttribute("id", makeWidgetId(extension.id) + "-panel");
- panel.setAttribute("class", "browser-extension-panel");
- panel.setAttribute("tabspecific", "true");
- panel.setAttribute("type", "arrow");
- panel.setAttribute("role", "group");
-
- document.getElementById("mainPopupSet").appendChild(panel);
-
- super(extension, panel, popupURL, browserStyle);
-
- this.contentReady.then(() => {
- panel.openPopup(imageNode, "bottomcenter topright", 0, 0, false, false);
-
- let event = new this.window.CustomEvent("WebExtPopupLoaded", {
- bubbles: true,
- detail: {extension},
- });
- this.browser.dispatchEvent(event);
- });
- }
-
- get DESTROY_EVENT() {
- return "popuphidden";
- }
-
- destroy() {
- super.destroy();
- this.viewNode.remove();
- this.viewNode = null;
- }
-
- closePopup() {
- promisePopupShown(this.viewNode).then(() => {
- // Make sure we're not already destroyed, or removed from the DOM.
- if (this.viewNode && this.viewNode.hidePopup) {
- this.viewNode.hidePopup();
- }
- });
- }
-}
-
-class ViewPopup extends BasePopup {
- constructor(extension, window, popupURL, browserStyle, fixedWidth) {
- let document = window.document;
-
- // Create a temporary panel to hold the browser while it pre-loads its
- // content. This panel will never be shown, but the browser's docShell will
- // be swapped with the browser in the real panel when it's ready.
- let panel = document.createElement("panel");
- panel.setAttribute("type", "arrow");
- document.getElementById("mainPopupSet").appendChild(panel);
-
- super(extension, panel, popupURL, browserStyle, fixedWidth);
-
- this.ignoreResizes = true;
-
- this.attached = false;
- this.shown = false;
- this.tempPanel = panel;
-
- this.browser.classList.add("webextension-preload-browser");
- }
-
- /**
- * Attaches the pre-loaded browser to the given view node, and reserves a
- * promise which resolves when the browser is ready.
- *
- * @param {Element} viewNode
- * The node to attach the browser to.
- * @returns {Promise<boolean>}
- * Resolves when the browser is ready. Resolves to `false` if the
- * browser was destroyed before it was fully loaded, and the popup
- * should be closed, or `true` otherwise.
- */
- attach(viewNode) {
- return Task.spawn(function* () {
- this.viewNode = viewNode;
- this.viewNode.addEventListener(this.DESTROY_EVENT, this);
-
- // Wait until the browser element is fully initialized, and give it at least
- // a short grace period to finish loading its initial content, if necessary.
- //
- // In practice, the browser that was created by the mousdown handler should
- // nearly always be ready by this point.
- yield Promise.all([
- this.browserReady,
- Promise.race([
- // This promise may be rejected if the popup calls window.close()
- // before it has fully loaded.
- this.browserLoaded.catch(() => {}),
- new Promise(resolve => setTimeout(resolve, POPUP_LOAD_TIMEOUT_MS)),
- ]),
- ]);
-
- if (!this.destroyed && !this.panel) {
- this.destroy();
- }
-
- if (this.destroyed) {
- CustomizableUI.hidePanelForNode(viewNode);
- return false;
- }
-
- this.attached = true;
-
-
- // Store the initial height of the view, so that we never resize menu panel
- // sub-views smaller than the initial height of the menu.
- this.viewHeight = this.viewNode.boxObject.height;
-
- // Calculate the extra height available on the screen above and below the
- // menu panel. Use that to calculate the how much the sub-view may grow.
- let popupRect = this.panel.getBoundingClientRect();
-
- this.setBackground(this.background);
-
- let win = this.window;
- let popupBottom = win.mozInnerScreenY + popupRect.bottom;
- let popupTop = win.mozInnerScreenY + popupRect.top;
-
- let screenBottom = win.screen.availTop + win.screen.availHeight;
- this.extraHeight = {
- bottom: Math.max(0, screenBottom - popupBottom),
- top: Math.max(0, popupTop - win.screen.availTop),
- };
-
- // Create a new browser in the real popup.
- let browser = this.browser;
- yield this.createBrowser(this.viewNode);
-
- this.ignoreResizes = false;
-
- this.browser.swapDocShells(browser);
- this.destroyBrowser(browser);
-
- if (this.dimensions && !this.fixedWidth) {
- this.resizeBrowser(this.dimensions);
- }
-
- this.tempPanel.remove();
- this.tempPanel = null;
-
- this.shown = true;
-
- if (this.destroyed) {
- this.closePopup();
- this.destroy();
- return false;
- }
-
- let event = new this.window.CustomEvent("WebExtPopupLoaded", {
- bubbles: true,
- detail: {extension: this.extension},
- });
- this.browser.dispatchEvent(event);
-
- return true;
- }.bind(this));
- }
-
- destroy() {
- return super.destroy().then(() => {
- if (this.tempPanel) {
- this.tempPanel.remove();
- this.tempPanel = null;
- }
- });
- }
-
- get DESTROY_EVENT() {
- return "ViewHiding";
- }
-
- closePopup() {
- if (this.shown) {
- CustomizableUI.hidePanelForNode(this.viewNode);
- } else if (this.attached) {
- this.destroyed = true;
- } else {
- this.destroy();
- }
- }
-}
-
-Object.assign(global, {PanelPopup, ViewPopup});
-
// Manages tab-specific context data, and dispatching tab select events
// across all windows.
global.TabContext = function TabContext(getDefaults, extension) {
this.extension = extension;
this.getDefaults = getDefaults;
this.tabData = new WeakMap();
this.lastLocation = new WeakMap();
- AllWindowEvents.addListener("progress", this);
- AllWindowEvents.addListener("TabSelect", this);
+ windowTracker.addListener("progress", this);
+ windowTracker.addListener("TabSelect", this);
EventEmitter.decorate(this);
};
TabContext.prototype = {
get(tab) {
if (!this.tabData.has(tab)) {
this.tabData.set(tab, this.getDefaults(tab));
@@ -599,430 +85,468 @@ TabContext.prototype = {
!(lastLocation && lastLocation.equalsExceptRef(browser.currentURI))) {
let tab = gBrowser.getTabForBrowser(browser);
this.emit("location-change", tab, true);
}
this.lastLocation.set(browser, browser.currentURI);
},
shutdown() {
- AllWindowEvents.removeListener("progress", this);
- AllWindowEvents.removeListener("TabSelect", this);
+ windowTracker.removeListener("progress", this);
+ windowTracker.removeListener("TabSelect", this);
},
};
-// Manages tab mappings and permissions for a specific extension.
-function ExtensionTabManager(extension) {
- this.extension = extension;
- // A mapping of tab objects to the inner window ID the extension currently has
- // the active tab permission for. The active permission for a given tab is
- // valid only for the inner window that was active when the permission was
- // granted. If the tab navigates, the inner window ID changes, and the
- // permission automatically becomes stale.
- //
- // WeakMap[tab => inner-window-id<int>]
- this.hasTabPermissionFor = new WeakMap();
+class WindowTracker extends WindowTrackerBase {
+ addProgressListener(window, listener) {
+ window.gBrowser.addTabsProgressListener(listener);
+ }
+
+ removeProgressListener(window, listener) {
+ window.gBrowser.removeTabsProgressListener(listener);
+ }
}
-ExtensionTabManager.prototype = {
- addActiveTabPermission(tab = TabManager.activeTab) {
- if (this.extension.hasPermission("activeTab")) {
- // Note that, unlike Chrome, we don't currently clear this permission with
- // the tab navigates. If the inner window is revived from BFCache before
- // we've granted this permission to a new inner window, the extension
- // maintains its permissions for it.
- this.hasTabPermissionFor.set(tab, tab.linkedBrowser.innerWindowID);
+global.WindowEventManager = class extends SingletonEventManager {
+ constructor(context, name, event, listener) {
+ super(context, name, fire => {
+ let listener2 = listener.bind(null, fire);
+
+ windowTracker.addListener(event, listener2);
+ return () => {
+ windowTracker.removeListener(event, listener2);
+ };
+ });
+ }
+};
+
+class TabTracker extends TabTrackerBase {
+ constructor() {
+ super();
+
+ this._tabs = new WeakMap();
+ this._tabIds = new Map();
+ this._nextId = 1;
+
+ this._handleTabDestroyed = this._handleTabDestroyed.bind(this);
+ }
+
+ init() {
+ if (this.initialized) {
+ return;
+ }
+ this.initialized = true;
+
+ this.adoptedTabs = new WeakMap();
+
+ this._handleWindowOpen = this._handleWindowOpen.bind(this);
+ this._handleWindowClose = this._handleWindowClose.bind(this);
+
+ windowTracker.addListener("TabClose", this);
+ windowTracker.addListener("TabOpen", this);
+ windowTracker.addOpenListener(this._handleWindowOpen);
+ windowTracker.addCloseListener(this._handleWindowClose);
+
+ /* eslint-disable mozilla/balanced-listeners */
+ this.on("tab-detached", this._handleTabDestroyed);
+ this.on("tab-removed", this._handleTabDestroyed);
+ /* eslint-enable mozilla/balanced-listeners */
+ }
+
+ getId(tab) {
+ if (this._tabs.has(tab)) {
+ return this._tabs.get(tab);
+ }
+
+ this.init();
+
+ let id = this._nextId++;
+ this.setId(tab, id);
+ return id;
+ }
+
+ setId(tab, id) {
+ this._tabs.set(tab, id);
+ this._tabIds.set(id, tab);
+ }
+
+ _handleTabDestroyed(event, {tab}) {
+ let id = this._tabs.get(tab);
+ if (id) {
+ this._tabs.delete(tab);
+ if (this._tabIds.get(id) === tab) {
+ this._tabIds.delete(id);
+ }
}
- },
+ }
+
+ /**
+ * Returns the XUL <tab> element associated with the given tab ID. If no tab
+ * with the given ID exists, and no default value is provided, an error is
+ * raised, belonging to the scope of the given context.
+ *
+ * @param {integer} tabId
+ * The ID of the tab to retrieve.
+ * @param {*} default_
+ * The value to return if no tab exists with the given ID.
+ * @returns {Element<tab>}
+ * A XUL <tab> element.
+ */
+ getTab(tabId, default_ = undefined) {
+ let tab = this._tabIds.get(tabId);
+ if (tab) {
+ return tab;
+ }
+ if (default_ !== undefined) {
+ return default_;
+ }
+ throw new ExtensionError(`Invalid tab ID: ${tabId}`);
+ }
+
+ handleEvent(event) {
+ let tab = event.target;
+
+ switch (event.type) {
+ case "TabOpen":
+ let {adoptedTab} = event.detail;
+ if (adoptedTab) {
+ this.adoptedTabs.set(adoptedTab, event.target);
- revokeActiveTabPermission(tab = TabManager.activeTab) {
- this.hasTabPermissionFor.delete(tab);
- },
+ // This tab is being created to adopt a tab from a different window.
+ // Copy the ID from the old tab to the new.
+ this.setId(tab, this.getId(adoptedTab));
+
+ adoptedTab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
+ windowId: windowTracker.getId(tab.ownerGlobal),
+ });
+ }
+
+ // We need to delay sending this event until the next tick, since the
+ // tab does not have its final index when the TabOpen event is dispatched.
+ Promise.resolve().then(() => {
+ if (event.detail.adoptedTab) {
+ this.emitAttached(event.originalTarget);
+ } else {
+ this.emitCreated(event.originalTarget);
+ }
+ });
+ break;
+
+ case "TabClose":
+ let {adoptedBy} = event.detail;
+ if (adoptedBy) {
+ // This tab is being closed because it was adopted by a new window.
+ // Copy its ID to the new tab, in case it was created as the first tab
+ // of a new window, and did not have an `adoptedTab` detail when it was
+ // opened.
+ this.setId(adoptedBy, this.getId(tab));
+
+ this.emitDetached(tab, adoptedBy);
+ } else {
+ this.emitRemoved(tab, false);
+ }
+ break;
+ }
+ }
- // Returns true if the extension has the "activeTab" permission for this tab.
- // This is somewhat more permissive than the generic "tabs" permission, as
- // checked by |hasTabPermission|, in that it also allows programmatic script
- // injection without an explicit host permission.
- hasActiveTabPermission(tab) {
- // This check is redundant with addTabPermission, but cheap.
- if (this.extension.hasPermission("activeTab")) {
- return (this.hasTabPermissionFor.has(tab) &&
- this.hasTabPermissionFor.get(tab) === tab.linkedBrowser.innerWindowID);
+ _handleWindowOpen(window) {
+ if (window.arguments && window.arguments[0] instanceof window.XULElement) {
+ // If the first window argument is a XUL element, it means the
+ // window is about to adopt a tab from another window to replace its
+ // initial tab.
+ //
+ // Note that this event handler depends on running before the
+ // delayed startup code in browser.js, which is currently triggered
+ // by the first MozAfterPaint event. That code handles finally
+ // adopting the tab, and clears it from the arguments list in the
+ // process, so if we run later than it, we're too late.
+ let tab = window.arguments[0];
+ let adoptedBy = window.gBrowser.tabs[0];
+
+ this.adoptedTabs.set(tab, adoptedBy);
+ this.setId(adoptedBy, this.getId(tab));
+
+ // We need to be sure to fire this event after the onDetached event
+ // for the original tab.
+ let listener = (event, details) => {
+ if (details.tab === tab) {
+ this.off("tab-detached", listener);
+
+ Promise.resolve().then(() => {
+ this.emitAttached(details.adoptedBy);
+ });
+ }
+ };
+
+ this.on("tab-detached", listener);
+ } else {
+ for (let tab of window.gBrowser.tabs) {
+ this.emitCreated(tab);
+ }
+ }
+ }
+
+ _handleWindowClose(window) {
+ for (let tab of window.gBrowser.tabs) {
+ if (this.adoptedTabs.has(tab)) {
+ this.emitDetached(tab, this.adoptedTabs.get(tab));
+ } else {
+ this.emitRemoved(tab, true);
+ }
}
- return false;
- },
+ }
+
+ emitAttached(tab) {
+ let newWindowId = windowTracker.getId(tab.ownerGlobal);
+ let tabId = this.getId(tab);
+
+ this.emit("tab-attached", {tab, tabId, newWindowId, newPosition: tab._tPos});
+ }
+
+ emitDetached(tab, adoptedBy) {
+ let oldWindowId = windowTracker.getId(tab.ownerGlobal);
+ let tabId = this.getId(tab);
+
+ this.emit("tab-detached", {tab, adoptedBy, tabId, oldWindowId, oldPosition: tab._tPos});
+ }
+
+ emitCreated(tab) {
+ this.emit("tab-created", {tab});
+ }
+
+ emitRemoved(tab, isWindowClosing) {
+ let windowId = windowTracker.getId(tab.ownerGlobal);
+ let tabId = this.getId(tab);
+
+ // When addons run in-process, `window.close()` is synchronous. Most other
+ // addon-invoked calls are asynchronous since they go through a proxy
+ // context via the message manager. This includes event registrations such
+ // as `tabs.onRemoved.addListener`.
+ //
+ // So, even if `window.close()` were to be called (in-process) after calling
+ // `tabs.onRemoved.addListener`, then the tab would be closed before the
+ // event listener is registered. To make sure that the event listener is
+ // notified, we dispatch `tabs.onRemoved` asynchronously.
+ Services.tm.mainThread.dispatch(() => {
+ this.emit("tab-removed", {tab, tabId, windowId, isWindowClosing});
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+
+ getBrowserData(browser) {
+ if (browser.ownerGlobal.location.href === "about:addons") {
+ // When we're loaded into a <browser> inside about:addons, we need to go up
+ // one more level.
+ browser = browser.ownerGlobal.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ }
+
+ let result = {
+ tabId: -1,
+ windowId: -1,
+ };
- hasTabPermission(tab) {
- return this.extension.hasPermission("tabs") || this.hasActiveTabPermission(tab);
- },
+ let {gBrowser} = browser.ownerGlobal;
+ // Some non-browser windows have gBrowser but not
+ // getTabForBrowser!
+ if (gBrowser && gBrowser.getTabForBrowser) {
+ result.windowId = windowTracker.getId(browser.ownerGlobal);
+
+ let tab = gBrowser.getTabForBrowser(browser);
+ if (tab) {
+ result.tabId = this.getId(tab);
+ }
+ }
+
+ return result;
+ }
+
+ get activeTab() {
+ let window = windowTracker.topWindow;
+ if (window && window.gBrowser) {
+ return window.gBrowser.selectedTab;
+ }
+ return null;
+ }
+}
+
+windowTracker = new WindowTracker();
+tabTracker = new TabTracker();
+
+Object.assign(global, {tabTracker, windowTracker});
- convert(tab) {
- let window = tab.ownerGlobal;
- let browser = tab.linkedBrowser;
+class Tab extends TabBase {
+ get _favIconUrl() {
+ return this.window.gBrowser.getIcon(this.tab);
+ }
+
+ get audible() {
+ return this.tab.soundPlaying;
+ }
+
+ get browser() {
+ return this.tab.linkedBrowser;
+ }
+
+ get cookieStoreId() {
+ return getCookieStoreIdForTab(this, this.tab);
+ }
+
+ get height() {
+ return this.browser.clientHeight;
+ }
+
+ get index() {
+ return this.tab._tPos;
+ }
+
+ get innerWindowID() {
+ return this.browser.innerWindowID;
+ }
+
+ get mutedInfo() {
+ let tab = this.tab;
let mutedInfo = {muted: tab.muted};
if (tab.muteReason === null) {
mutedInfo.reason = "user";
} else if (tab.muteReason) {
mutedInfo.reason = "extension";
mutedInfo.extensionId = tab.muteReason;
}
- let result = {
- id: TabManager.getId(tab),
- index: tab._tPos,
- windowId: WindowManager.getId(window),
- selected: tab.selected,
- highlighted: tab.selected,
- active: tab.selected,
- pinned: tab.pinned,
- status: TabManager.getStatus(tab),
- incognito: WindowManager.isBrowserPrivate(browser),
- width: browser.frameLoader.lazyWidth || browser.clientWidth,
- height: browser.frameLoader.lazyHeight || browser.clientHeight,
- audible: tab.soundPlaying,
- mutedInfo,
- };
- if (this.extension.hasPermission("cookies")) {
- result.cookieStoreId = getCookieStoreIdForTab(result, tab);
- }
+ return mutedInfo;
+ }
+
+ get pinned() {
+ return this.tab.pinned;
+ }
+
+ get active() {
+ return this.tab.selected;
+ }
+
+ get selected() {
+ return this.tab.selected;
+ }
- if (this.hasTabPermission(tab)) {
- result.url = browser.currentURI.spec;
- let title = browser.contentTitle || tab.label;
- if (title) {
- result.title = title;
- }
- let icon = window.gBrowser.getIcon(tab);
- if (icon) {
- result.favIconUrl = icon;
- }
+ get status() {
+ if (this.tab.getAttribute("busy") === "true") {
+ return "loading";
}
+ return "complete";
+ }
- return result;
- },
+ get width() {
+ return this.browser.clientWidth;
+ }
- // Converts tabs returned from SessionStore.getClosedTabData and
- // SessionStore.getClosedWindowData into API tab objects
- convertFromSessionStoreClosedData(tab, window) {
+ get window() {
+ return this.tab.ownerGlobal;
+ }
+
+ get windowId() {
+ return windowTracker.getId(this.window);
+ }
+
+ static convertFromSessionStoreClosedData(extension, tab, window = null) {
let result = {
sessionId: String(tab.closedId),
index: tab.pos ? tab.pos : 0,
- windowId: WindowManager.getId(window),
+ windowId: window && windowTracker.getId(window),
selected: false,
highlighted: false,
active: false,
pinned: false,
incognito: Boolean(tab.state && tab.state.isPrivate),
};
- if (this.hasTabPermission(tab)) {
+ if (extension.tabManager.hasTabPermission(tab)) {
let entries = tab.state ? tab.state.entries : tab.entries;
result.url = entries[0].url;
result.title = entries[0].title;
if (tab.image) {
result.favIconUrl = tab.image;
}
}
return result;
- },
-
- getTabs(window) {
- return Array.from(window.gBrowser.tabs)
- .filter(tab => !tab.closing)
- .map(tab => this.convert(tab));
- },
-};
-
-function getBrowserInfo(browser) {
- if (!browser.ownerGlobal.gBrowser) {
- // When we're loaded into a <browser> inside about:addons, we need to go up
- // one more level.
- browser = browser.ownerGlobal.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDocShell)
- .chromeEventHandler;
-
- if (!browser) {
- return {};
- }
}
-
- let result = {};
-
- let window = browser.ownerGlobal;
- if (window.gBrowser) {
- let tab = window.gBrowser.getTabForBrowser(browser);
- if (tab) {
- result.tabId = TabManager.getId(tab);
- }
-
- result.windowId = WindowManager.getId(window);
- }
-
- return result;
-}
-global.getBrowserInfo = getBrowserInfo;
-
-// Sends the tab and windowId upon request. This is primarily used to support
-// the synchronous `browser.extension.getViews` API.
-let onGetTabAndWindowId = {
- receiveMessage({name, target, sync}) {
- let result = getBrowserInfo(target);
-
- if (result.tabId) {
- if (sync) {
- return result;
- }
- target.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", result);
- }
- },
-};
-/* eslint-disable mozilla/balanced-listeners */
-Services.mm.addMessageListener("Extension:GetTabAndWindowId", onGetTabAndWindowId);
-/* eslint-enable mozilla/balanced-listeners */
-
-
-// Manages global mappings between XUL tabs and extension tab IDs.
-global.TabManager = {
- _tabs: new WeakMap(),
- _nextId: 1,
- _initialized: false,
-
- // We begin listening for TabOpen and TabClose events once we've started
- // assigning IDs to tabs, so that we can remap the IDs of tabs which are moved
- // between windows.
- initListener() {
- if (this._initialized) {
- return;
- }
-
- AllWindowEvents.addListener("TabOpen", this);
- AllWindowEvents.addListener("TabClose", this);
- WindowListManager.addOpenListener(this.handleWindowOpen.bind(this));
-
- this._initialized = true;
- },
-
- handleEvent(event) {
- if (event.type == "TabOpen") {
- let {adoptedTab} = event.detail;
- if (adoptedTab) {
- // This tab is being created to adopt a tab from a different window.
- // Copy the ID from the old tab to the new.
- let tab = event.target;
- this._tabs.set(tab, this.getId(adoptedTab));
-
- tab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
- windowId: WindowManager.getId(tab.ownerGlobal),
- });
- }
- } else if (event.type == "TabClose") {
- let {adoptedBy} = event.detail;
- if (adoptedBy) {
- // This tab is being closed because it was adopted by a new window.
- // Copy its ID to the new tab, in case it was created as the first tab
- // of a new window, and did not have an `adoptedTab` detail when it was
- // opened.
- this._tabs.set(adoptedBy, this.getId(event.target));
-
- adoptedBy.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
- windowId: WindowManager.getId(adoptedBy),
- });
- }
- }
- },
-
- handleWindowOpen(window) {
- if (window.arguments && window.arguments[0] instanceof window.XULElement) {
- // If the first window argument is a XUL element, it means the
- // window is about to adopt a tab from another window to replace its
- // initial tab.
- let adoptedTab = window.arguments[0];
-
- this._tabs.set(window.gBrowser.tabs[0], this.getId(adoptedTab));
- }
- },
-
- getId(tab) {
- if (this._tabs.has(tab)) {
- return this._tabs.get(tab);
- }
- this.initListener();
-
- let id = this._nextId++;
- this._tabs.set(tab, id);
- return id;
- },
-
- getBrowserId(browser) {
- let gBrowser = browser.ownerGlobal.gBrowser;
- // Some non-browser windows have gBrowser but not
- // getTabForBrowser!
- if (gBrowser && gBrowser.getTabForBrowser) {
- let tab = gBrowser.getTabForBrowser(browser);
- if (tab) {
- return this.getId(tab);
- }
- }
- return -1;
- },
-
- /**
- * Returns the XUL <tab> element associated with the given tab ID. If no tab
- * with the given ID exists, and no default value is provided, an error is
- * raised, belonging to the scope of the given context.
- *
- * @param {integer} tabId
- * The ID of the tab to retrieve.
- * @param {ExtensionContext} context
- * The context of the caller.
- * This value may be omitted if `default_` is not `undefined`.
- * @param {*} default_
- * The value to return if no tab exists with the given ID.
- * @returns {Element<tab>}
- * A XUL <tab> element.
- */
- getTab(tabId, context, default_ = undefined) {
- // FIXME: Speed this up without leaking memory somehow.
- for (let window of WindowListManager.browserWindows()) {
- if (!window.gBrowser) {
- continue;
- }
- for (let tab of window.gBrowser.tabs) {
- if (this.getId(tab) == tabId) {
- return tab;
- }
- }
- }
- if (default_ !== undefined) {
- return default_;
- }
- throw new context.cloneScope.Error(`Invalid tab ID: ${tabId}`);
- },
-
- get activeTab() {
- let window = WindowManager.topWindow;
- if (window && window.gBrowser) {
- return window.gBrowser.selectedTab;
- }
- return null;
- },
-
- getStatus(tab) {
- return tab.getAttribute("busy") == "true" ? "loading" : "complete";
- },
-
- convert(extension, tab) {
- return TabManager.for(extension).convert(tab);
- },
-};
-
-// WeakMap[Extension -> ExtensionTabManager]
-let tabManagers = new WeakMap();
-
-// Returns the extension-specific tab manager for the given extension, or
-// creates one if it doesn't already exist.
-TabManager.for = function(extension) {
- if (!tabManagers.has(extension)) {
- tabManagers.set(extension, new ExtensionTabManager(extension));
- }
- return tabManagers.get(extension);
-};
-
-/* eslint-disable mozilla/balanced-listeners */
-extensions.on("shutdown", (type, extension) => {
- tabManagers.delete(extension);
-});
-/* eslint-enable mozilla/balanced-listeners */
-
-function memoize(fn) {
- let weakMap = new DefaultWeakMap(fn);
- return weakMap.get.bind(weakMap);
}
-// Manages mapping between XUL windows and extension window IDs.
-global.WindowManager = {
- // Note: These must match the values in windows.json.
- WINDOW_ID_NONE: -1,
- WINDOW_ID_CURRENT: -2,
-
- get topWindow() {
- return Services.wm.getMostRecentWindow("navigator:browser");
- },
-
- windowType(window) {
- // TODO: Make this work.
+class Window extends WindowBase {
+ updateGeometry(options) {
+ let {window} = this;
- let {chromeFlags} = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDocShell)
- .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIXULWindow);
-
- if (chromeFlags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG) {
- return "popup";
- }
-
- return "normal";
- },
-
- updateGeometry(window, options) {
if (options.left !== null || options.top !== null) {
let left = options.left !== null ? options.left : window.screenX;
let top = options.top !== null ? options.top : window.screenY;
window.moveTo(left, top);
}
if (options.width !== null || options.height !== null) {
let width = options.width !== null ? options.width : window.outerWidth;
let height = options.height !== null ? options.height : window.outerHeight;
window.resizeTo(width, height);
}
- },
+ }
- isBrowserPrivate: memoize(browser => {
- return PrivateBrowsingUtils.isBrowserPrivate(browser);
- }),
+ get focused() {
+ return this.window.document.hasFocus();
+ }
- getId: memoize(window => {
- if (window instanceof Ci.nsIInterfaceRequestor) {
- return window.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
- }
- return null;
- }),
+ get top() {
+ return this.window.screenY;
+ }
+
+ get left() {
+ return this.window.screenX;
+ }
+
+ get width() {
+ return this.window.outerWidth;
+ }
- getWindow(id, context) {
- if (id == this.WINDOW_ID_CURRENT) {
- return currentWindow(context);
- }
+ get height() {
+ return this.window.outerHeight;
+ }
+
+ get incognito() {
+ return PrivateBrowsingUtils.isWindowPrivate(this.window);
+ }
- for (let window of WindowListManager.browserWindows(true)) {
- if (this.getId(window) == id) {
- return window;
- }
- }
- return null;
- },
+ get alwaysOnTop() {
+ return this.xulWindow.zLevel >= Ci.nsIXULWindow.raisedZ;
+ }
- getState(window) {
+ get isLastFocused() {
+ return this.window === windowTracker.topWindow;
+ }
+
+ static getState(window) {
const STATES = {
[window.STATE_MAXIMIZED]: "maximized",
[window.STATE_MINIMIZED]: "minimized",
[window.STATE_NORMAL]: "normal",
};
let state = STATES[window.windowState];
if (window.fullScreen) {
state = "fullscreen";
}
return state;
- },
+ }
- setState(window, state) {
- if (state != "fullscreen" && window.fullScreen) {
+ get state() {
+ return Window.getState(this.window);
+ }
+
+ set state(state) {
+ let {window} = this;
+ if (state !== "fullscreen" && window.fullScreen) {
window.fullScreen = false;
}
switch (state) {
case "maximized":
window.maximize();
break;
@@ -1031,266 +555,106 @@ global.WindowManager = {
window.minimize();
break;
case "normal":
// Restore sometimes returns the window to its previous state, rather
// than to the "normal" state, so it may need to be called anywhere from
// zero to two times.
window.restore();
- if (window.windowState != window.STATE_NORMAL) {
+ if (window.windowState !== window.STATE_NORMAL) {
window.restore();
}
- if (window.windowState != window.STATE_NORMAL) {
+ if (window.windowState !== window.STATE_NORMAL) {
// And on OS-X, where normal vs. maximized is basically a heuristic,
// we need to cheat.
window.sizeToContent();
}
break;
case "fullscreen":
window.fullScreen = true;
break;
default:
throw new Error(`Unexpected window state: ${state}`);
}
- },
+ }
- convert(extension, window, getInfo) {
- let xulWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDocShell)
- .treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIXULWindow);
+ * getTabs() {
+ let {tabManager} = this.extension;
- let result = {
- id: this.getId(window),
- focused: window.document.hasFocus(),
- top: window.screenY,
- left: window.screenX,
- width: window.outerWidth,
- height: window.outerHeight,
- incognito: PrivateBrowsingUtils.isWindowPrivate(window),
- type: this.windowType(window),
- state: this.getState(window),
- alwaysOnTop: xulWindow.zLevel >= Ci.nsIXULWindow.raisedZ,
- };
+ for (let tab of this.window.gBrowser.tabs) {
+ yield tabManager.getWrapper(tab);
+ }
+ }
- if (getInfo && getInfo.populate) {
- result.tabs = TabManager.for(extension).getTabs(window);
- }
-
- return result;
- },
-
- // Converts windows returned from SessionStore.getClosedWindowData
- // into API window objects
- convertFromSessionStoreClosedData(window, extension) {
+ static convertFromSessionStoreClosedData(extension, window) {
let result = {
sessionId: String(window.closedId),
focused: false,
incognito: false,
type: "normal", // this is always "normal" for a closed window
+ // Surely this does not actually work?
state: this.getState(window),
alwaysOnTop: false,
};
if (window.tabs.length) {
- result.tabs = [];
- window.tabs.forEach((tab, index) => {
- result.tabs.push(TabManager.for(extension).convertFromSessionStoreClosedData(tab, window, index));
+ result.tabs = window.tabs.map(tab => {
+ return Tab.convertFromSessionStoreClosedData(extension, tab);
});
}
return result;
- },
-};
-
-// Manages listeners for window opening and closing. A window is
-// considered open when the "load" event fires on it. A window is
-// closed when a "domwindowclosed" notification fires for it.
-global.WindowListManager = {
- _openListeners: new Set(),
- _closeListeners: new Set(),
+ }
+}
- // Returns an iterator for all browser windows. Unless |includeIncomplete| is
- // true, only fully-loaded windows are returned.
- * browserWindows(includeIncomplete = false) {
- // The window type parameter is only available once the window's document
- // element has been created. This means that, when looking for incomplete
- // browser windows, we need to ignore the type entirely for windows which
- // haven't finished loading, since we would otherwise skip browser windows
- // in their early loading stages.
- // This is particularly important given that the "domwindowcreated" event
- // fires for browser windows when they're in that in-between state, and just
- // before we register our own "domwindowcreated" listener.
-
- let e = Services.wm.getEnumerator("");
- while (e.hasMoreElements()) {
- let window = e.getNext();
-
- let ok = includeIncomplete;
- if (window.document.readyState == "complete") {
- ok = window.document.documentElement.getAttribute("windowtype") == "navigator:browser";
- }
-
- if (ok) {
- yield window;
- }
- }
- },
-
- addOpenListener(listener) {
- if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
- Services.ww.registerNotification(this);
- }
- this._openListeners.add(listener);
+Object.assign(global, {Tab, Window});
- for (let window of this.browserWindows(true)) {
- if (window.document.readyState != "complete") {
- window.addEventListener("load", this);
- }
- }
- },
-
- removeOpenListener(listener) {
- this._openListeners.delete(listener);
- if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
- Services.ww.unregisterNotification(this);
- }
- },
-
- addCloseListener(listener) {
- if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
- Services.ww.registerNotification(this);
- }
- this._closeListeners.add(listener);
- },
+class TabManager extends TabManagerBase {
+ get(tabId, default_ = undefined) {
+ let tab = tabTracker.getTab(tabId, default_);
- removeCloseListener(listener) {
- this._closeListeners.delete(listener);
- if (this._openListeners.size == 0 && this._closeListeners.size == 0) {
- Services.ww.unregisterNotification(this);
- }
- },
-
- handleEvent(event) {
- event.currentTarget.removeEventListener(event.type, this);
- let window = event.target.defaultView;
- if (window.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
- return;
- }
-
- for (let listener of this._openListeners) {
- listener(window);
- }
- },
-
- observe(window, topic, data) {
- if (topic == "domwindowclosed") {
- if (window.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
- return;
- }
-
- window.removeEventListener("load", this);
- for (let listener of this._closeListeners) {
- listener(window);
- }
- } else {
- window.addEventListener("load", this);
+ if (tab) {
+ return this.getWrapper(tab);
}
- },
-};
-
-// Provides a facility to listen for DOM events across all XUL windows.
-global.AllWindowEvents = {
- _listeners: new Map(),
+ return default_;
+ }
- // If |type| is a normal event type, invoke |listener| each time
- // that event fires in any open window. If |type| is "progress", add
- // a web progress listener that covers all open windows.
- addListener(type, listener) {
- if (type == "domwindowopened") {
- return WindowListManager.addOpenListener(listener);
- } else if (type == "domwindowclosed") {
- return WindowListManager.addCloseListener(listener);
- }
-
- if (this._listeners.size == 0) {
- WindowListManager.addOpenListener(this.openListener);
- }
+ addActiveTabPermission(tab = tabTracker.activeTab) {
+ return super.addActiveTabPermission(tab);
+ }
- if (!this._listeners.has(type)) {
- this._listeners.set(type, new Set());
- }
- let list = this._listeners.get(type);
- list.add(listener);
-
- // Register listener on all existing windows.
- for (let window of WindowListManager.browserWindows()) {
- this.addWindowListener(window, type, listener);
- }
- },
+ revokeActiveTabPermission(tab = tabTracker.activeTab) {
+ return super.revokeActiveTabPermission(tab);
+ }
- removeListener(eventType, listener) {
- if (eventType == "domwindowopened") {
- return WindowListManager.removeOpenListener(listener);
- } else if (eventType == "domwindowclosed") {
- return WindowListManager.removeCloseListener(listener);
- }
+ wrapTab(tab) {
+ return new Tab(this.extension, tab, tabTracker.getId(tab));
+ }
+}
- let listeners = this._listeners.get(eventType);
- listeners.delete(listener);
- if (listeners.size == 0) {
- this._listeners.delete(eventType);
- if (this._listeners.size == 0) {
- WindowListManager.removeOpenListener(this.openListener);
- }
- }
+class WindowManager extends WindowManagerBase {
+ get(windowId, context) {
+ let window = windowTracker.getWindow(windowId, context);
- // Unregister listener from all existing windows.
- let useCapture = eventType === "focus" || eventType === "blur";
- for (let window of WindowListManager.browserWindows()) {
- if (eventType == "progress") {
- window.gBrowser.removeTabsProgressListener(listener);
- } else {
- window.removeEventListener(eventType, listener, useCapture);
- }
- }
- },
+ return this.getWrapper(window);
+ }
- /* eslint-disable mozilla/balanced-listeners */
- addWindowListener(window, eventType, listener) {
- let useCapture = eventType === "focus" || eventType === "blur";
-
- if (eventType == "progress") {
- window.gBrowser.addTabsProgressListener(listener);
- } else {
- window.addEventListener(eventType, listener, useCapture);
+ * getAll() {
+ for (let window of windowTracker.browserWindows()) {
+ yield this.getWrapper(window);
}
- },
- /* eslint-enable mozilla/balanced-listeners */
+ }
- // Runs whenever the "load" event fires for a new window.
- openListener(window) {
- for (let [eventType, listeners] of AllWindowEvents._listeners) {
- for (let listener of listeners) {
- this.addWindowListener(window, eventType, listener);
- }
- }
- },
-};
+ wrapWindow(window) {
+ return new Window(this.extension, window, windowTracker.getId(window));
+ }
+}
-AllWindowEvents.openListener = AllWindowEvents.openListener.bind(AllWindowEvents);
-// Subclass of SingletonEventManager where we just need to call
-// add/removeEventListener on each XUL window.
-global.WindowEventManager = class extends SingletonEventManager {
- constructor(context, name, event, listener) {
- super(context, name, fire => {
- let listener2 = (...args) => listener(fire, ...args);
- AllWindowEvents.addListener(event, listener2);
- return () => {
- AllWindowEvents.removeListener(event, listener2);
- };
- });
- }
-};
+extensions.on("startup", (type, extension) => { // eslint-disable-line mozilla/balanced-listeners
+ defineLazyGetter(extension, "tabManager",
+ () => new TabManager(extension));
+ defineLazyGetter(extension, "windowManager",
+ () => new WindowManager(extension));
+});
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -17,73 +17,76 @@ var {
} = ExtensionUtils;
function onXULFrameLoaderCreated({target}) {
target.messageManager.sendAsyncMessage("AllowScriptsToClose", {});
}
extensions.registerSchemaAPI("windows", "addon_parent", context => {
let {extension} = context;
+
+ const {windowManager} = extension;
+
return {
windows: {
onCreated:
new WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
- fire.async(WindowManager.convert(extension, window));
+ fire.async(windowManager.convert(window));
}).api(),
onRemoved:
new WindowEventManager(context, "windows.onRemoved", "domwindowclosed", (fire, window) => {
- fire.async(WindowManager.getId(window));
+ fire.async(windowTracker.getId(window));
}).api(),
onFocusChanged: new SingletonEventManager(context, "windows.onFocusChanged", fire => {
// Keep track of the last windowId used to fire an onFocusChanged event
let lastOnFocusChangedWindowId;
let listener = event => {
// Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
// event when switching focus between two Firefox windows.
Promise.resolve().then(() => {
let window = Services.focus.activeWindow;
- let windowId = window ? WindowManager.getId(window) : WindowManager.WINDOW_ID_NONE;
+ let windowId = window ? windowTracker.getId(window) : Window.WINDOW_ID_NONE;
if (windowId !== lastOnFocusChangedWindowId) {
fire.async(windowId);
lastOnFocusChangedWindowId = windowId;
}
});
};
- AllWindowEvents.addListener("focus", listener);
- AllWindowEvents.addListener("blur", listener);
+ windowTracker.addListener("focus", listener);
+ windowTracker.addListener("blur", listener);
return () => {
- AllWindowEvents.removeListener("focus", listener);
- AllWindowEvents.removeListener("blur", listener);
+ windowTracker.removeListener("focus", listener);
+ windowTracker.removeListener("blur", listener);
};
}).api(),
get: function(windowId, getInfo) {
- let window = WindowManager.getWindow(windowId, context);
+ let window = windowTracker.getWindow(windowId, context);
if (!window) {
return Promise.reject({message: `Invalid window ID: ${windowId}`});
}
- return Promise.resolve(WindowManager.convert(extension, window, getInfo));
+ return Promise.resolve(windowManager.convert(window, getInfo));
},
getCurrent: function(getInfo) {
- let window = currentWindow(context);
- return Promise.resolve(WindowManager.convert(extension, window, getInfo));
+ let window = context.currentWindow || windowTracker.topWindow;
+ return Promise.resolve(windowManager.convert(window, getInfo));
},
getLastFocused: function(getInfo) {
- let window = WindowManager.topWindow;
- return Promise.resolve(WindowManager.convert(extension, window, getInfo));
+ let window = windowTracker.topWindow;
+ return Promise.resolve(windowManager.convert(window, getInfo));
},
getAll: function(getInfo) {
- let windows = Array.from(WindowListManager.browserWindows(),
- window => WindowManager.convert(extension, window, getInfo));
+ let windows = Array.from(windowManager.getAll(), win => win.convert(getInfo));
+
return Promise.resolve(windows);
},
create: function(createData) {
let needResize = (createData.left !== null || createData.top !== null ||
createData.width !== null || createData.height !== null);
if (needResize) {
@@ -105,17 +108,17 @@ extensions.registerSchemaAPI("windows",
if (createData.url !== null) {
return Promise.reject({message: "`tabId` may not be used in conjunction with `url`"});
}
if (createData.allowScriptsToClose) {
return Promise.reject({message: "`tabId` may not be used in conjunction with `allowScriptsToClose`"});
}
- let tab = TabManager.getTab(createData.tabId, context);
+ let tab = tabTracker.getTab(createData.tabId);
// Private browsing tabs can only be moved to private browsing
// windows.
let incognito = PrivateBrowsingUtils.isBrowserPrivate(tab.linkedBrowser);
if (createData.incognito !== null && createData.incognito != incognito) {
return Promise.reject({message: "`incognito` property must match the incognito state of tab"});
}
createData.incognito = incognito;
@@ -155,79 +158,80 @@ extensions.registerSchemaAPI("windows",
let {allowScriptsToClose, url} = createData;
if (allowScriptsToClose === null) {
allowScriptsToClose = typeof url === "string" && url.startsWith("moz-extension://");
}
let window = Services.ww.openWindow(null, "chrome://browser/content/browser.xul", "_blank",
features.join(","), args);
- WindowManager.updateGeometry(window, createData);
+ let win = windowManager.getWrapper(window);
+ win.updateGeometry(createData);
// TODO: focused, type
return new Promise(resolve => {
window.addEventListener("load", function() {
if (["maximized", "normal"].includes(createData.state)) {
window.document.documentElement.setAttribute("sizemode", createData.state);
}
resolve(promiseObserved("browser-delayed-startup-finished", win => win == window));
}, {once: true});
}).then(() => {
// Some states only work after delayed-startup-finished
if (["minimized", "fullscreen", "docked"].includes(createData.state)) {
- WindowManager.setState(window, createData.state);
+ win.state = createData.state;
}
if (allowScriptsToClose) {
for (let {linkedBrowser} of window.gBrowser.tabs) {
onXULFrameLoaderCreated({target: linkedBrowser});
linkedBrowser.addEventListener( // eslint-disable-line mozilla/balanced-listeners
"XULFrameLoaderCreated", onXULFrameLoaderCreated);
}
}
- return WindowManager.convert(extension, window, {populate: true});
+ return win.convert({populate: true});
});
},
update: function(windowId, updateInfo) {
if (updateInfo.state !== null && updateInfo.state != "normal") {
if (updateInfo.left !== null || updateInfo.top !== null ||
updateInfo.width !== null || updateInfo.height !== null) {
return Promise.reject({message: `"state": "${updateInfo.state}" may not be combined with "left", "top", "width", or "height"`});
}
}
- let window = WindowManager.getWindow(windowId, context);
+ let win = windowManager.get(windowId, context);
if (updateInfo.focused) {
- Services.focus.activeWindow = window;
+ Services.focus.activeWindow = win.window;
}
if (updateInfo.state !== null) {
- WindowManager.setState(window, updateInfo.state);
+ win.state = updateInfo.state;
}
if (updateInfo.drawAttention) {
// Bug 1257497 - Firefox can't cancel attention actions.
- window.getAttention();
+ win.window.getAttention();
}
- WindowManager.updateGeometry(window, updateInfo);
+ win.updateGeometry(updateInfo);
// TODO: All the other properties, focused=false...
- return Promise.resolve(WindowManager.convert(extension, window));
+ return Promise.resolve(win.convert());
},
remove: function(windowId) {
- let window = WindowManager.getWindow(windowId, context);
+ let window = windowTracker.getWindow(windowId, context);
window.close();
return new Promise(resolve => {
let listener = () => {
- AllWindowEvents.removeListener("domwindowclosed", listener);
+ windowTracker.removeListener("domwindowclosed", listener);
resolve();
};
- AllWindowEvents.addListener("domwindowclosed", listener);
+ windowTracker.addListener("domwindowclosed", listener);
});
},
},
};
});
--- a/browser/components/extensions/moz.build
+++ b/browser/components/extensions/moz.build
@@ -5,16 +5,20 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
JAR_MANIFESTS += ['jar.mn']
EXTRA_COMPONENTS += [
'extensions-browser.manifest',
]
+EXTRA_JS_MODULES += [
+ 'ExtensionPopups.jsm',
+]
+
DIRS += ['schemas']
BROWSER_CHROME_MANIFESTS += [
'test/browser/browser-remote.ini',
'test/browser/browser.ini',
]
MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
--- a/browser/components/extensions/test/browser/browser_ext_currentWindow.js
+++ b/browser/components/extensions/test/browser/browser_ext_currentWindow.js
@@ -85,20 +85,20 @@ add_task(function* () {
"popup.js": genericChecker,
},
background: genericChecker,
});
yield Promise.all([extension.startup(), extension.awaitMessage("background-ready")]);
- let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+ let {Management: {global: {windowTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
- let winId1 = WindowManager.getId(win1);
- let winId2 = WindowManager.getId(win2);
+ let winId1 = windowTracker.getId(win1);
+ let winId2 = windowTracker.getId(win2);
function* checkWindow(kind, winId, name) {
extension.sendMessage(kind + "-check-current1");
is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 1) [${kind}]`);
extension.sendMessage(kind + "-check-current2");
is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 2) [${kind}]`);
extension.sendMessage(kind + "-check-current3");
is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 3) [${kind}]`);
--- a/browser/components/extensions/test/browser/browser_ext_getViews.js
+++ b/browser/components/extensions/test/browser/browser_ext_getViews.js
@@ -99,20 +99,20 @@ add_task(function* () {
background: genericChecker,
});
yield Promise.all([extension.startup(), extension.awaitMessage("background-ready")]);
info("started");
- let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+ let {Management: {global: {windowTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
- let winId1 = WindowManager.getId(win1);
- let winId2 = WindowManager.getId(win2);
+ let winId1 = windowTracker.getId(win1);
+ let winId2 = windowTracker.getId(win2);
function* openTab(winId) {
extension.sendMessage("background-open-tab", winId);
yield extension.awaitMessage("tab-ready");
}
function* checkViews(kind, tabCount, popupCount, kindCount, windowId = undefined, windowCount = 0) {
extension.sendMessage(kind + "-check-views", windowId);
--- a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js
@@ -27,18 +27,18 @@ add_task(function* test_sessions_get_rec
background,
});
// Open a private browsing window.
let privateWin = yield BrowserTestUtils.openNewBrowserWindow({private: true});
yield extension.startup();
- let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
- let privateWinId = WindowManager.getId(privateWin);
+ let {Management: {global: {windowTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+ let privateWinId = windowTracker.getId(privateWin);
extension.sendMessage("check-sessions");
let recentlyClosed = yield extension.awaitMessage("recentlyClosed");
recordInitialTimestamps(recentlyClosed.map(item => item.lastModified));
// Open and close two tabs in the private window
let tab = yield BrowserTestUtils.openNewForegroundTab(privateWin.gBrowser, "http://example.com");
yield BrowserTestUtils.removeTab(tab);
--- a/browser/components/extensions/test/browser/browser_ext_sessions_restore.js
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_restore.js
@@ -50,20 +50,20 @@ add_task(function* test_sessions_restore
function* assertNotificationCount(expected) {
let notificationCount = yield extension.awaitMessage("notificationCount");
is(notificationCount, expected, "the expected number of notifications was fired");
}
yield extension.startup();
- let {Management: {global: {WindowManager, TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+ let {Management: {global: {windowTracker, tabTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
function checkLocalTab(tab, expectedUrl) {
- let realTab = TabManager.getTab(tab.id);
+ let realTab = tabTracker.getTab(tab.id);
let tabState = JSON.parse(SessionStore.getTabState(realTab));
is(tabState.entries[0].url, expectedUrl, "restored tab has the expected url");
}
yield extension.awaitMessage("ready");
let win = yield BrowserTestUtils.openNewBrowserWindow();
yield BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, "about:config");
@@ -87,17 +87,17 @@ add_task(function* test_sessions_restore
is(restored.length, 1, "restore returned the expected number of sessions");
is(restored[0].window.tabs.length, 3, "restore returned a window with the expected number of tabs");
checkLocalTab(restored[0].window.tabs[0], "about:config");
checkLocalTab(restored[0].window.tabs[1], "about:robots");
checkLocalTab(restored[0].window.tabs[2], "about:mozilla");
// Close the window again.
- let window = WindowManager.getWindow(restored[0].window.id);
+ let window = windowTracker.getWindow(restored[0].window.id);
yield BrowserTestUtils.closeWindow(window);
yield assertNotificationCount(3);
// Restore the window using the sessionId.
extension.sendMessage("check-sessions");
recentlyClosed = yield extension.awaitMessage("recentlyClosed");
extension.sendMessage("restore", recentlyClosed[0].window.sessionId);
yield assertNotificationCount(4);
@@ -105,17 +105,17 @@ add_task(function* test_sessions_restore
is(restored.length, 1, "restore returned the expected number of sessions");
is(restored[0].window.tabs.length, 3, "restore returned a window with the expected number of tabs");
checkLocalTab(restored[0].window.tabs[0], "about:config");
checkLocalTab(restored[0].window.tabs[1], "about:robots");
checkLocalTab(restored[0].window.tabs[2], "about:mozilla");
// Close the window again.
- window = WindowManager.getWindow(restored[0].window.id);
+ window = windowTracker.getWindow(restored[0].window.id);
yield BrowserTestUtils.closeWindow(window);
// notificationCount = yield extension.awaitMessage("notificationCount");
yield assertNotificationCount(5);
// Open and close a tab.
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
yield TabStateFlusher.flush(tab.linkedBrowser);
yield BrowserTestUtils.removeTab(tab);
@@ -127,34 +127,34 @@ add_task(function* test_sessions_restore
restored = yield extension.awaitMessage("restored");
is(restored.length, 1, "restore returned the expected number of sessions");
tab = restored[0].tab;
ok(tab, "restore returned a tab");
checkLocalTab(tab, "about:robots");
// Close the tab again.
- let realTab = TabManager.getTab(tab.id);
+ let realTab = tabTracker.getTab(tab.id);
yield BrowserTestUtils.removeTab(realTab);
yield assertNotificationCount(8);
// Restore the tab using the sessionId.
extension.sendMessage("check-sessions");
recentlyClosed = yield extension.awaitMessage("recentlyClosed");
extension.sendMessage("restore", recentlyClosed[0].tab.sessionId);
yield assertNotificationCount(9);
restored = yield extension.awaitMessage("restored");
is(restored.length, 1, "restore returned the expected number of sessions");
tab = restored[0].tab;
ok(tab, "restore returned a tab");
checkLocalTab(tab, "about:robots");
// Close the tab again.
- realTab = TabManager.getTab(tab.id);
+ realTab = tabTracker.getTab(tab.id);
yield BrowserTestUtils.removeTab(realTab);
yield assertNotificationCount(10);
// Try to restore something with an invalid sessionId.
extension.sendMessage("restore-reject");
restored = yield extension.awaitMessage("restore-rejected");
yield extension.unload();
--- a/browser/components/extensions/test/browser/browser_ext_tabs_audio.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_audio.js
@@ -156,19 +156,19 @@ add_task(function* () {
manifest: {
"permissions": ["tabs"],
},
background,
});
extension.onMessage("change-tab", (tabId, attr, on) => {
- let {Management: {global: {TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+ let {Management: {global: {tabTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
- let tab = TabManager.getTab(tabId);
+ let tab = tabTracker.getTab(tabId);
if (attr == "muted") {
// Ideally we'd simulate a click on the tab audio icon for this, but the
// handler relies on CSS :hover states, which are complicated and fragile
// to simulate.
if (tab.muted != on) {
tab.toggleMuteAudio();
}
@@ -179,17 +179,17 @@ add_task(function* () {
} else {
browser.audioPlaybackStopped();
}
} else if (attr == "duplicate") {
// This is a bit of a hack. It won't be necessary once we have
// `tabs.duplicate`.
let newTab = gBrowser.duplicateTab(tab);
BrowserTestUtils.waitForEvent(newTab, "SSTabRestored", () => true).then(() => {
- extension.sendMessage("change-tab-done", tabId, TabManager.getId(newTab));
+ extension.sendMessage("change-tab-done", tabId, tabTracker.getId(newTab));
});
return;
}
extension.sendMessage("change-tab-done", tabId);
});
yield extension.startup();
--- a/browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_duplicate.js
@@ -89,24 +89,24 @@ add_task(function* testDuplicateTabLazil
manifest: {
"permissions": ["tabs"],
},
background,
});
extension.onMessage("duplicate-tab", tabId => {
- let {Management: {global: {TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+ let {Management: {global: {tabTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
- let tab = TabManager.getTab(tabId);
+ let tab = tabTracker.getTab(tabId);
// This is a bit of a hack to load a tab in the background.
let newTab = gBrowser.duplicateTab(tab, true);
BrowserTestUtils.waitForEvent(newTab, "SSTabRestored", () => true).then(() => {
- extension.sendMessage("duplicate-tab-done", TabManager.getId(newTab));
+ extension.sendMessage("duplicate-tab-done", tabTracker.getId(newTab));
});
});
yield extension.startup();
yield extension.awaitFinish("tabs.hasCorrectTabTitle");
yield extension.unload();
});
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_no_create.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_no_create.js
@@ -16,17 +16,17 @@ add_task(function* testExecuteScriptAtOn
function background() {
// Using variables to prevent listeners from running more than once, instead
// of removing the listener. This is to minimize any IPC, since the bug that
// is being tested is sensitive to timing.
let ignore = false;
let url;
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
- if (changeInfo.status === "loading" && tab.url === url && !ignore) {
+ if (url && changeInfo.status === "loading" && tab.url === url && !ignore) {
ignore = true;
browser.tabs.executeScript(tabId, {
code: "document.URL",
}).then(results => {
browser.test.assertEq(url, results[0], "Content script should run");
browser.test.notifyPass("executeScript-at-onUpdated");
}, error => {
browser.test.fail(`Unexpected error: ${error} :: ${error.stack}`);
--- a/browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_zoom.js
@@ -184,24 +184,24 @@ add_task(function* () {
manifest: {
"permissions": ["tabs"],
},
background,
});
extension.onMessage("msg", (id, msg, ...args) => {
- let {Management: {global: {TabManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+ let {Management: {global: {tabTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
let resp;
if (msg == "get-zoom") {
- let tab = TabManager.getTab(args[0]);
+ let tab = tabTracker.getTab(args[0]);
resp = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
} else if (msg == "set-zoom") {
- let tab = TabManager.getTab(args[0]);
+ let tab = tabTracker.getTab(args[0]);
ZoomManager.setZoomForBrowser(tab.linkedBrowser);
} else if (msg == "enlarge") {
FullZoom.enlarge();
} else if (msg == "site-specific") {
if (args[0] == null) {
SpecialPowers.clearUserPref(SITE_SPECIFIC_PREF);
} else {
SpecialPowers.setBoolPref(SITE_SPECIFIC_PREF, args[0]);
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js
@@ -1,23 +1,23 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* testWebNavigationGetNonExistentTab() {
let extension = ExtensionTestUtils.loadExtension({
background: async function() {
- // There is no "tabId = 0" because the id assigned by TabManager (defined in ext-utils.js)
+ // There is no "tabId = 0" because the id assigned by tabTracker (defined in ext-utils.js)
// starts from 1.
await browser.test.assertRejects(
browser.webNavigation.getAllFrames({tabId: 0}),
"Invalid tab ID: 0",
"getAllFrames rejected Promise should pass the expected error");
- // There is no "tabId = 0" because the id assigned by TabManager (defined in ext-utils.js)
+ // There is no "tabId = 0" because the id assigned by tabTracker (defined in ext-utils.js)
// starts from 1, processId is currently marked as optional and it is ignored.
await browser.test.assertRejects(
browser.webNavigation.getFrame({tabId: 0, frameId: 15, processId: 20}),
"Invalid tab ID: 0",
"getFrame rejected Promise should pass the expected error");
browser.test.sendMessage("getNonExistentTab.done");
},
--- a/browser/components/extensions/test/browser/browser_ext_windows_events.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_events.js
@@ -56,20 +56,20 @@ add_task(function* testWindowsEvents() {
let extension = ExtensionTestUtils.loadExtension({
background: `(${background})()`,
});
yield extension.startup();
yield extension.awaitMessage("ready");
- let {Management: {global: {WindowManager}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
+ let {Management: {global: {windowTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
let currentWindow = window;
- let currentWindowId = WindowManager.getId(currentWindow);
+ let currentWindowId = windowTracker.getId(currentWindow);
info(`Current window ID: ${currentWindowId}`);
info(`Create browser window 1`);
let win1 = yield BrowserTestUtils.openNewBrowserWindow();
let win1Id = yield extension.awaitMessage("window-created");
info(`Window 1 ID: ${win1Id}`);
// This shouldn't be necessary, but tests intermittently fail, so let's give
--- a/build/valgrind/x86_64-redhat-linux-gnu.sup
+++ b/build/valgrind/x86_64-redhat-linux-gnu.sup
@@ -203,16 +203,47 @@
Bug 1288618 comments 119 through 127 part 2
Memcheck:Cond
fun:__get_cpuid
fun:cpuid
fun:_ZN6SkOptsL4initEv
fun:sk_once_no_arg_adaptor
}
+# More stuff to do with CPUID and Skia. Apparently we could get rid of
+# these if we could patch our in-tree Skia, but that's not favoured.
+#
+# Conditional jump or move depends on uninitialised value(s)
+# at 0xFDD1D97: SkCpu::CacheRuntimeFeatures()
+# by 0xFE8A66E: SkGraphics::Init()
+# by 0xE757308: gfxPlatform::Init()
+# by 0xE75772C: gfxPlatform::GetPlatform()
+{
+ Skia and CPUID, Jan 2017, #1
+ Memcheck:Cond
+ fun:_ZN5SkCpu20CacheRuntimeFeaturesEv
+ fun:_ZN10SkGraphics4InitEv
+ fun:_ZN11gfxPlatform4InitEv
+ fun:_ZN11gfxPlatform11GetPlatformEv
+}
+
+# Conditional jump or move depends on uninitialised value(s)
+# at 0xFD5B218: SkOpts::Init()
+# by 0xE757308: gfxPlatform::Init()
+# by 0xE75772C: gfxPlatform::GetPlatform()
+# by 0xF1A3691: mozilla::dom::ContentProcess::Init()
+{
+ Skia and CPUID, Jan 2017, #2
+ Memcheck:Cond
+ fun:_ZN6SkOpts4InitEv
+ fun:_ZN11gfxPlatform4InitEv
+ fun:_ZN11gfxPlatform11GetPlatformEv
+ fun:_ZN7mozilla3dom14ContentProcess4InitEv
+}
+
###################################################
# For valgrind-mochitest ("tc-M-V [tier 2]") runs on taskcluster.
# See bug 1248365.
# These are specific to Ubuntu 12.04.5, 64-bit.
###################################################
@@ -427,16 +458,30 @@
Bug 1248365: FastConvertYUVToRGB32Row-1
Memcheck:Value8
fun:FastConvertYUVToRGB32Row
fun:_ZN7mozilla3gfx19ConvertYCbCrToRGB32*
fun:_ZN7mozilla3gfx17ConvertYCbCrToRGB*
fun:_ZN7mozilla6layers16PlanarYCbCrImage18GetAsSourceSurface*
}
+# Similarly:
+# Conditional jump or move depends on uninitialised value(s)
+# at 0xFDAD1D1: sse41::blit_row_s32a_opaque(unsigned int*, unsigned int con
+# by 0xFD60FA9: Sprite_D32_S32::blitRect(int, int, int, int) (in /home/work
+# by 0xFEB9E0D: SkScan::FillIRect(SkIRect const&, SkRegion const*, SkBlitte
+# by 0xFEBDDF3: SkScan::FillIRect(SkIRect const&, SkRasterClip const&, SkBl
+{
+ SKIA and SSE4, Jan 2017
+ Memcheck:Cond
+ fun:_ZN5sse41L20blit_row_s32a_opaque*
+ fun:_ZN14Sprite_D32_S328blitRect*
+ fun:_ZN6SkScan9FillIRect*
+ fun:_ZN6SkScan9FillIRect*
+}
# This is probably a V false positive, due to an insufficiently accurate
# description of the ioctl(SIOCETHTOOL) behavior.
# Syscall param ioctl(SIOCETHTOOL) points to uninitialised byte(s)
# at 0x5D5CBF7: ioctl (syscall-template.S:82)
# by 0xF58EB67: nr_stun_get_addrs (in /home/worker/workspace/build/applica
# by 0xF594791: nr_stun_find_local_addresses (in /home/worker/workspace/bu
# by 0xF58A237: nr_ice_get_local_addresses (in /home/worker/workspace/buil
@@ -483,19 +528,19 @@
obj:/*/libpthread*.so*
obj:/*/libfontconfig.so*
...
obj:/*/libfontconfig.so*
fun:FcConfigAppFontAddDir
}
-# There's nothing we can do about this short of throwing in
+# There's nothing we can do about these short of throwing in
# --show-mismatched-frees=no, but that's a bit drastic, so for now,
-# just suppress it.
+# just suppress them. A typical error is:
#
# Mismatched free() / delete / delete []
# at 0x4C2BE97: free (vg_replace_malloc.c:530)
# by 0xFCD09EC: ots::ots_post_free(ots::Font*) (in /home/worker/workspace/
# by 0xFCC600E: ots::Font::~Font() (in /home/worker/workspace/build/applic
# by 0xFCCBFA5: ots::OTSContext::Process(ots::OTSStream*, unsigned char co
# by 0xE7D7C8D: gfxUserFontEntry::SanitizeOpenTypeData(unsigned char const
# by 0xE7E371D: gfxUserFontEntry::LoadPlatformFont(unsigned char const*, u
@@ -513,8 +558,58 @@
{
Bug 1248365: ots::Font::~Font()-1
Memcheck:Free
fun:free
fun:_ZN3ots13ots_post_free*
fun:_ZN3ots4FontD1Ev
fun:_ZN3ots10OTSContext7Process*
}
+
+# and various similar:
+{
+ ots mismatched frees, Jan 2017, #1
+ Memcheck:Free
+ fun:_ZdlPv
+ fun:_ZN3ots14ots_glyf_parse*
+ fun:_ZN12_GLOBAL__N_114ProcessGenericEPN3ots12OpenTypeFile*
+ fun:_ZN12_GLOBAL__N_110ProcessTTFEPN3ots12OpenTypeFile*
+}
+{
+ ots mismatched frees, Jan 2017, #2
+ Memcheck:Free
+ fun:_ZdlPv
+ fun:_ZN3ots13ots_cff_parse*
+ fun:_ZN12_GLOBAL__N_114ProcessGenericEPN3ots12OpenTypeFile*
+ fun:_ZN3ots10OTSContext7ProcessEPNS_9OTSStream*
+}
+{
+ ots mismatched frees, Jan 2017, #3
+ Memcheck:Free
+ fun:_ZdlPv
+ fun:_ZN3ots13ots_cff_parse*
+ fun:_ZN12_GLOBAL__N_114ProcessGenericEPN3ots12OpenTypeFile*
+ fun:_ZN12_GLOBAL__N_110Process*
+}
+{
+ ots mismatched frees, Jan 2017, #4
+ Memcheck:Free
+ fun:_ZdlPv
+ fun:_ZN3ots12ots_cff_free*
+ fun:_ZN3ots4FontD1Ev*
+ fun:_ZN3ots10OTSContext7Process*
+}
+{
+ ots mismatched frees, Jan 2017, #5
+ Memcheck:Free
+ fun:_ZdlPv
+ fun:_ZN3ots13ots_loca_free*
+ fun:_ZN3ots4FontD1Ev*
+ fun:_ZN3ots10OTSContext7Process*
+}
+{
+ ots mismatched frees, Jan 2017, #6
+ Memcheck:Free
+ fun:_ZdlPv
+ fun:_ZN3ots14ots_glyf_parse*
+ fun:_ZN12_GLOBAL__N_114ProcessGenericEPN3ots12OpenTypeFile*
+ fun:_ZN3ots10OTSContext7ProcessEPNS_9OTSStream*
+}
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -517,29 +517,16 @@ nsScriptSecurityManager::CheckLoadURIFro
nsIPrincipal* principal = nsContentUtils::SubjectPrincipal();
nsresult rv = CheckLoadURIWithPrincipal(principal, aURI,
nsIScriptSecurityManager::STANDARD);
if (NS_SUCCEEDED(rv)) {
// OK to load
return NS_OK;
}
- // See if we're attempting to load a file: URI. If so, let a
- // UniversalXPConnect capability trump the above check.
- bool isFile = false;
- bool isRes = false;
- if (NS_FAILED(aURI->SchemeIs("file", &isFile)) ||
- NS_FAILED(aURI->SchemeIs("resource", &isRes)))
- return NS_ERROR_FAILURE;
- if (isFile || isRes)
- {
- if (nsContentUtils::IsCallerChrome())
- return NS_OK;
- }
-
// Report error.
nsAutoCString spec;
if (NS_FAILED(aURI->GetAsciiSpec(spec)))
return NS_ERROR_FAILURE;
nsAutoCString msg("Access to '");
msg.Append(spec);
msg.AppendLiteral("' from script denied");
SetPendingExceptionASCII(cx, msg.get());
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -178,19 +178,19 @@ function CssComputedView(inspector, docu
this.searchClearButton = doc.getElementById("computedview-searchinput-clear");
this.includeBrowserStylesCheckbox =
doc.getElementById("browser-style-checkbox");
this.shortcuts = new KeyShortcuts({ window: this.styleWindow });
this._onShortcut = this._onShortcut.bind(this);
this.shortcuts.on("CmdOrCtrl+F", this._onShortcut);
this.shortcuts.on("Escape", this._onShortcut);
+ this.styleDocument.addEventListener("copy", this._onCopy);
this.styleDocument.addEventListener("mousedown", this.focusWindow);
this.element.addEventListener("click", this._onClick);
- this.element.addEventListener("copy", this._onCopy);
this.element.addEventListener("contextmenu", this._onContextMenu);
this.searchField.addEventListener("input", this._onFilterStyles);
this.searchField.addEventListener("contextmenu", this.inspector.onTextBoxContextMenu);
this.searchClearButton.addEventListener("click", this._onClearSearch);
this.includeBrowserStylesCheckbox.addEventListener("input",
this._onIncludeBrowserStyles);
this.searchClearButton.hidden = true;
@@ -682,42 +682,57 @@ CssComputedView.prototype = {
/**
* Callback for copy event. Copy selected text.
*
* @param {Event} event
* copy event object.
*/
_onCopy: function (event) {
- this.copySelection();
- event.preventDefault();
+ let win = this.styleWindow;
+ let text = win.getSelection().toString().trim();
+ if (text !== "") {
+ this.copySelection();
+ event.preventDefault();
+ }
},
/**
* Copy the current selection to the clipboard
*/
copySelection: function () {
try {
let win = this.styleWindow;
let text = win.getSelection().toString().trim();
-
+ // isPropertyPresent is set when a property name is spotted and
+ // we assume that the next line will be a property value.
+ let isPropertyPresent = false;
// Tidy up block headings by moving CSS property names and their
// values onto the same line and inserting a colon between them.
let textArray = text.split(/[\r\n]+/);
let result = "";
// Parse text array to output string.
if (textArray.length > 1) {
for (let prop of textArray) {
if (CssComputedView.propertyNames.indexOf(prop) !== -1) {
+ // Property name found so setting isPropertyPresent to true
+ isPropertyPresent = true;
// Property name
result += prop;
+ } else if (isPropertyPresent === true) {
+ // Since isPropertyPresent is true so we assume that this is
+ // a property value and we append it to result preceeded by
+ // a :.
+ result += ": " + prop + ";\n";
+ isPropertyPresent = false;
} else {
- // Property value
- result += ": " + prop + ";\n";
+ // since isPropertyPresent is not set, we assume this is
+ // normal text and we append it to result without any :.
+ result += prop + "\n";
}
}
} else {
// Short text fragment.
result = textArray[0];
}
clipboardHelper.copyString(result);
@@ -752,17 +767,17 @@ CssComputedView.prototype = {
}
this.tooltips.destroy();
this.highlighters.removeFromView(this);
// Remove bound listeners
this.styleDocument.removeEventListener("mousedown", this.focusWindow);
this.element.removeEventListener("click", this._onClick);
- this.element.removeEventListener("copy", this._onCopy);
+ this.styleDocument.removeEventListener("copy", this._onCopy);
this.element.removeEventListener("contextmenu", this._onContextMenu);
this.searchField.removeEventListener("input", this._onFilterStyles);
this.searchField.removeEventListener("contextmenu",
this.inspector.onTextBoxContextMenu);
this.searchClearButton.removeEventListener("click", this._onClearSearch);
this.includeBrowserStylesCheckbox.removeEventListener("input",
this._onIncludeBrowserStyles);
--- a/devtools/client/performance/modules/logic/tree-model.js
+++ b/devtools/client/performance/modules/logic/tree-model.js
@@ -50,17 +50,17 @@ function ThreadNode(thread, options = {}
this._uninvert();
}
}
ThreadNode.prototype = {
/**
* Build an inverted call tree from profile samples. The format of the
* samples is described in tools/profiler/ProfileEntry.h, under the heading
- * "ThreadProfile JSON Format".
+ * "Thread profile JSON Format".
*
* The profile data is naturally presented inverted. Inverting the call tree
* is also the default in the Performance tool.
*
* @param object samples
* The raw samples array received from the backend.
* @param object stackTable
* The table of deduplicated stacks from the backend.
--- a/devtools/client/shared/components/tabs/tabs.css
+++ b/devtools/client/shared/components/tabs/tabs.css
@@ -25,18 +25,25 @@
display: block;
color: #A9A9A9;
padding: 4px 8px;
border: 1px solid transparent;
text-decoration: none;
white-space: nowrap;
}
+/* To avoid "select all" commands from selecting content in hidden tabs */
+.tabs .hidden,
+.tabs .hidden * {
+ -moz-user-select: none !important;
+}
+
.tabs .tabs-menu-item a {
cursor: default;
+ -moz-user-select: none;
}
/* Make sure panel content takes entire vertical space.
(minus the height of the tab bar) */
.tabs .panels {
height: calc(100% - 24px);
}
--- a/devtools/client/shared/components/tabs/tabs.js
+++ b/devtools/client/shared/components/tabs/tabs.js
@@ -324,17 +324,17 @@ define(function (require, exports, modul
width: selected ? "100%" : "0",
};
return (
DOM.div({
id: id ? id + "-panel" : "panel-" + index,
key: index,
style: style,
- className: "tab-panel-box",
+ className: selected ? "tab-panel-box" : "tab-panel-box hidden",
role: "tabpanel",
"aria-labelledby": id ? id + "-tab" : "tab-" + index,
},
(selected || this.state.created[index]) ? tab : null
)
);
});
--- a/devtools/client/themes/boxmodel.css
+++ b/devtools/client/themes/boxmodel.css
@@ -221,17 +221,17 @@
.boxmodel-legend[data-box="margin"] {
color: var(--theme-highlight-blue);
}
/* Editable fields */
.boxmodel-editable {
border: 1px dashed transparent;
- -moz-user-select: text;
+ -moz-user-select: none;
}
.boxmodel-editable:hover {
border-bottom-color: hsl(0, 0%, 50%);
}
/* Make sure the content size doesn't appear as editable like the other sizes */
--- a/devtools/client/themes/computed.css
+++ b/devtools/client/themes/computed.css
@@ -24,16 +24,17 @@
#computedview-container-focusable {
height: 100%;
outline: none;
}
#computedview-toolbar {
display: flex;
align-items: center;
+ -moz-user-select: none;
}
#browser-style-checkbox {
/* Bug 1200073 - extra space before the browser styles checkbox so
they aren't squished together in a small window. Put also
an extra space after. */
margin-inline-start: 5px;
margin-inline-end: 0;
--- a/devtools/client/themes/deprecated-boxmodel.css
+++ b/devtools/client/themes/deprecated-boxmodel.css
@@ -6,16 +6,17 @@
* THIS STYLESHEET IS FOR THE DEPRECATED BOX MODEL MODULE (deprecated-box-model.js) AND
* SHOULD NO LONGER BE MODIFIED.
*/
#old-boxmodel-wrapper {
border-bottom-style: solid;
border-bottom-width: 1px;
border-color: var(--theme-splitter-color);
+ -moz-user-select: none;
}
#old-boxmodel-container {
/* The view will grow bigger as the window gets resized, until 400px */
max-width: 400px;
margin: 0px auto;
padding: 0;
}
@@ -228,17 +229,17 @@
.old-boxmodel-legend[data-box="margin"] {
color: var(--theme-highlight-blue);
}
/* Editable fields */
.old-boxmodel-editable {
border: 1px dashed transparent;
- -moz-user-select: text;
+ -moz-user-select: none;
}
.old-boxmodel-editable:hover {
border-bottom-color: hsl(0, 0%, 50%);
}
/* Make sure the content size doesn't appear as editable like the other sizes */
--- a/devtools/shared/fronts/call-watcher.js
+++ b/devtools/shared/fronts/call-watcher.js
@@ -50,19 +50,16 @@ protocol.FrontClassWithSpec(callWatcherS
*/
CallWatcherFront.METHOD_FUNCTION = 0;
CallWatcherFront.GETTER_FUNCTION = 1;
CallWatcherFront.SETTER_FUNCTION = 2;
CallWatcherFront.KNOWN_METHODS = {};
CallWatcherFront.KNOWN_METHODS.CanvasRenderingContext2D = {
- asyncDrawXULElement: {
- enums: new Set([6]),
- },
drawWindow: {
enums: new Set([6])
},
};
CallWatcherFront.KNOWN_METHODS.WebGLRenderingContext = {
activeTexture: {
enums: new Set([0]),
--- a/docshell/test/navigation/mochitest.ini
+++ b/docshell/test/navigation/mochitest.ini
@@ -45,17 +45,17 @@ support-files =
[test_bug270414.html]
[test_bug278916.html]
[test_bug279495.html]
[test_bug344861.html]
skip-if = toolkit == "android" || toolkit == "windows" # disabled on Windows because of bug 1234520
[test_bug386782.html]
[test_bug430624.html]
[test_bug430723.html]
-skip-if = toolkit == 'android' #TIMED_OUT
+skip-if = (toolkit == 'android') || (!debug && (os == 'mac' || os == 'win')) # Bug 874423
[test_child.html]
[test_grandchild.html]
[test_not-opener.html]
[test_opener.html]
[test_popup-navigates-children.html]
[test_reserved.html]
skip-if = (toolkit == 'android') || (debug && e10s) #too slow on Android 4.3 aws only; bug 1030403; bug 1263213 for debug e10s
[test_sessionhistory.html]
--- a/dom/base/DOMParser.cpp
+++ b/dom/base/DOMParser.cpp
@@ -379,17 +379,17 @@ DOMParser::Init(nsIPrincipal* principal,
return NS_OK;
}
/*static */already_AddRefed<DOMParser>
DOMParser::Constructor(const GlobalObject& aOwner,
nsIPrincipal* aPrincipal, nsIURI* aDocumentURI,
nsIURI* aBaseURI, ErrorResult& rv)
{
- if (!nsContentUtils::IsCallerChrome()) {
+ if (aOwner.CallerType() != CallerType::System) {
rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
RefPtr<DOMParser> domParser = new DOMParser(aOwner.GetAsSupports());
rv = domParser->InitInternal(aOwner.GetAsSupports(), aPrincipal, aDocumentURI,
aBaseURI);
if (rv.Failed()) {
return nullptr;
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3316,36 +3316,36 @@ Element::AttrValueToCORSMode(const nsAtt
if (!aValue) {
return CORS_NONE;
}
return CORSMode(aValue->GetEnumValue());
}
static const char*
-GetFullScreenError(nsIDocument* aDoc)
+GetFullScreenError(CallerType aCallerType)
{
- if (!nsContentUtils::IsRequestFullScreenAllowed()) {
+ if (!nsContentUtils::IsRequestFullScreenAllowed(aCallerType)) {
return "FullscreenDeniedNotInputDriven";
}
return nullptr;
}
void
Element::RequestFullscreen(CallerType aCallerType, ErrorResult& aError)
{
// Only grant full-screen requests if this is called from inside a trusted
// event handler (i.e. inside an event handler for a user initiated event).
// This stops the full-screen from being abused similar to the popups of old,
// and it also makes it harder for bad guys' script to go full-screen and
// spoof the browser chrome/window and phish logins etc.
// Note that requests for fullscreen inside a web app's origin are exempt
// from this restriction.
- if (const char* error = GetFullScreenError(OwnerDoc())) {
+ if (const char* error = GetFullScreenError(aCallerType)) {
OwnerDoc()->DispatchFullscreenError(error);
return;
}
auto request = MakeUnique<FullscreenRequest>(this);
request->mIsCallerChrome = (aCallerType == CallerType::System);
OwnerDoc()->AsyncRequestFullScreen(Move(request));
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1419,16 +1419,17 @@ Navigator::GetMediaDevices(ErrorResult&
}
return mMediaDevices;
}
void
Navigator::MozGetUserMedia(const MediaStreamConstraints& aConstraints,
NavigatorUserMediaSuccessCallback& aOnSuccess,
NavigatorUserMediaErrorCallback& aOnError,
+ CallerType aCallerType,
ErrorResult& aRv)
{
CallbackObjectHolder<NavigatorUserMediaSuccessCallback,
nsIDOMGetUserMediaSuccessCallback> holder1(&aOnSuccess);
nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onsuccess =
holder1.ToXPCOMCallback();
CallbackObjectHolder<NavigatorUserMediaErrorCallback,
@@ -1437,17 +1438,18 @@ Navigator::MozGetUserMedia(const MediaSt
if (!mWindow || !mWindow->GetOuterWindow() ||
mWindow->GetOuterWindow()->GetCurrentInnerWindow() != mWindow) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return;
}
MediaManager* manager = MediaManager::Get();
- aRv = manager->GetUserMedia(mWindow, aConstraints, onsuccess, onerror);
+ aRv = manager->GetUserMedia(mWindow, aConstraints, onsuccess, onerror,
+ aCallerType);
}
void
Navigator::MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
NavigatorUserMediaErrorCallback& aOnError,
uint64_t aInnerWindowID,
const nsAString& aCallID,
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -227,16 +227,17 @@ public:
bool SendBeacon(const nsAString& aUrl,
const Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>& aData,
ErrorResult& aRv);
void MozGetUserMedia(const MediaStreamConstraints& aConstraints,
NavigatorUserMediaSuccessCallback& aOnSuccess,
NavigatorUserMediaErrorCallback& aOnError,
+ CallerType aCallerType,
ErrorResult& aRv);
void MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
NavigatorUserMediaErrorCallback& aOnError,
uint64_t aInnerWindowID,
const nsAString& aCallID,
ErrorResult& aRv);
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -6842,33 +6842,33 @@ nsContentUtils::ChannelShouldInheritPrin
bool
nsContentUtils::IsFullScreenApiEnabled()
{
return sIsFullScreenApiEnabled;
}
/* static */
bool
-nsContentUtils::IsRequestFullScreenAllowed()
+nsContentUtils::IsRequestFullScreenAllowed(CallerType aCallerType)
{
return !sTrustedFullScreenOnly ||
EventStateManager::IsHandlingUserInput() ||
- IsCallerChrome();
+ aCallerType == CallerType::System;
}
/* static */
bool
-nsContentUtils::IsCutCopyAllowed()
+nsContentUtils::IsCutCopyAllowed(nsIPrincipal* aSubjectPrincipal)
{
if ((!IsCutCopyRestricted() && EventStateManager::IsHandlingUserInput()) ||
- IsCallerChrome()) {
+ IsSystemPrincipal(aSubjectPrincipal)) {
return true;
}
- return BasePrincipal::Cast(SubjectPrincipal())->AddonHasPermission(NS_LITERAL_STRING("clipboardWrite"));
+ return BasePrincipal::Cast(aSubjectPrincipal)->AddonHasPermission(NS_LITERAL_STRING("clipboardWrite"));
}
/* static */
bool
nsContentUtils::IsFrameTimingEnabled()
{
return sIsFrameTimingPrefEnabled;
}
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1998,33 +1998,33 @@ public:
{ return sIsUnprefixedFullscreenApiEnabled; }
/**
* Returns true if requests for full-screen are allowed in the current
* context. Requests are only allowed if the user initiated them (like with
* a mouse-click or key press), unless this check has been disabled by
* setting the pref "full-screen-api.allow-trusted-requests-only" to false.
*/
- static bool IsRequestFullScreenAllowed();
+ static bool IsRequestFullScreenAllowed(mozilla::dom::CallerType aCallerType);
/**
* Returns true if calling execCommand with 'cut' or 'copy' arguments
* is restricted to chrome code.
*/
static bool IsCutCopyRestricted()
{
return !sIsCutCopyAllowed;
}
/**
* Returns true if calling execCommand with 'cut' or 'copy' arguments is
- * allowed in the current context. These are only allowed if the user initiated
- * them (like with a mouse-click or key press).
+ * allowed for the given subject principal. These are only allowed if the user
+ * initiated them (like with a mouse-click or key press).
*/
- static bool IsCutCopyAllowed();
+ static bool IsCutCopyAllowed(nsIPrincipal* aSubjectPrincipal);
/*
* Returns true if the performance timing APIs are enabled.
*/
static bool IsPerformanceTimingEnabled()
{
return sIsPerformanceTimingEnabled;
}
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -7281,19 +7281,20 @@ nsGlobalWindow::Confirm(const nsAString&
ErrorResult& aError)
{
FORWARD_TO_OUTER_OR_THROW(ConfirmOuter, (aMessage, aSubjectPrincipal, aError),
aError, false);
}
already_AddRefed<Promise>
nsGlobalWindow::Fetch(const RequestOrUSVString& aInput,
- const RequestInit& aInit, ErrorResult& aRv)
-{
- return FetchRequest(this, aInput, aInit, aRv);
+ const RequestInit& aInit,
+ CallerType aCallerType, ErrorResult& aRv)
+{
+ return FetchRequest(this, aInput, aInit, aCallerType, aRv);
}
void
nsGlobalWindow::PromptOuter(const nsAString& aMessage,
const nsAString& aInitial,
nsAString& aReturn,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError)
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -959,16 +959,17 @@ public:
mozilla::ErrorResult& aError);
void Prompt(const nsAString& aMessage, const nsAString& aInitial,
nsAString& aReturn,
nsIPrincipal& aSubjectPrincipal,
mozilla::ErrorResult& aError);
already_AddRefed<mozilla::dom::cache::CacheStorage> GetCaches(mozilla::ErrorResult& aRv);
already_AddRefed<mozilla::dom::Promise> Fetch(const mozilla::dom::RequestOrUSVString& aInput,
const mozilla::dom::RequestInit& aInit,
+ mozilla::dom::CallerType aCallerType,
mozilla::ErrorResult& aRv);
void PrintOuter(mozilla::ErrorResult& aError);
void Print(mozilla::ErrorResult& aError);
void ShowModalDialog(JSContext* aCx, const nsAString& aUrl,
JS::Handle<JS::Value> aArgument,
const nsAString& aOptions,
JS::MutableHandle<JS::Value> aRetval,
nsIPrincipal& aSubjectPrincipal,
@@ -1204,17 +1205,17 @@ public:
mozilla::IgnoredErrorResult ignored;
nsCOMPtr<nsPIDOMWindowOuter> win =
GetContentInternal(ignored, mozilla::dom::CallerType::System);
return win.forget();
}
void Get_content(JSContext* aCx,
JS::MutableHandle<JSObject*> aRetval,
- mozilla::dom::CallerType aCallerType,
+ mozilla::dom::SystemCallerGuarantee aCallerType,
mozilla::ErrorResult& aError)
{
if (mDoc) {
mDoc->WarnOnceAbout(nsIDocument::eWindow_Content);
}
GetContent(aCx, aRetval, aCallerType, aError);
}
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -101,16 +101,17 @@
#include "DocumentType.h"
#include <algorithm>
#include "nsGlobalWindow.h"
#include "nsDOMMutationObserver.h"
#include "GeometryUtils.h"
#include "nsIAnimationObserver.h"
#include "nsChildContentList.h"
#include "mozilla/dom/NodeBinding.h"
+#include "mozilla/dom/BindingDeclarations.h"
#ifdef ACCESSIBILITY
#include "mozilla/dom/AccessibleNode.h"
#endif
using namespace mozilla;
using namespace mozilla::dom;
@@ -714,19 +715,21 @@ nsINode::GetBaseURI(nsAString &aURI) con
NS_ENSURE_SUCCESS(rv, rv);
}
CopyUTF8toUTF16(spec, aURI);
return NS_OK;
}
void
-nsINode::GetBaseURIFromJS(nsAString& aURI, ErrorResult& aRv) const
+nsINode::GetBaseURIFromJS(nsAString& aURI,
+ CallerType aCallerType,
+ ErrorResult& aRv) const
{
- nsCOMPtr<nsIURI> baseURI = GetBaseURI(nsContentUtils::IsCallerChrome());
+ nsCOMPtr<nsIURI> baseURI = GetBaseURI(aCallerType == CallerType::System);
nsAutoCString spec;
if (baseURI) {
nsresult res = baseURI->GetSpec(spec);
if (NS_FAILED(res)) {
aRv.Throw(res);
return;
}
}
@@ -1260,46 +1263,53 @@ nsINode::GetEventTargetParent(EventChain
// This is only here so that we can use the NS_DECL_NSIDOMTARGET macro
NS_ABORT();
return NS_ERROR_NOT_IMPLEMENTED;
}
void
nsINode::GetBoxQuads(const BoxQuadOptions& aOptions,
nsTArray<RefPtr<DOMQuad> >& aResult,
+ CallerType aCallerType,
mozilla::ErrorResult& aRv)
{
- mozilla::GetBoxQuads(this, aOptions, aResult, aRv);
+ mozilla::GetBoxQuads(this, aOptions, aResult, aCallerType, aRv);
}
already_AddRefed<DOMQuad>
nsINode::ConvertQuadFromNode(DOMQuad& aQuad,
const GeometryNode& aFrom,
const ConvertCoordinateOptions& aOptions,
+ CallerType aCallerType,
ErrorResult& aRv)
{
- return mozilla::ConvertQuadFromNode(this, aQuad, aFrom, aOptions, aRv);
+ return mozilla::ConvertQuadFromNode(this, aQuad, aFrom, aOptions, aCallerType,
+ aRv);
}
already_AddRefed<DOMQuad>
nsINode::ConvertRectFromNode(DOMRectReadOnly& aRect,
const GeometryNode& aFrom,
const ConvertCoordinateOptions& aOptions,
+ CallerType aCallerType,
ErrorResult& aRv)
{
- return mozilla::ConvertRectFromNode(this, aRect, aFrom, aOptions, aRv);
+ return mozilla::ConvertRectFromNode(this, aRect, aFrom, aOptions, aCallerType,
+ aRv);
}
already_AddRefed<DOMPoint>
nsINode::ConvertPointFromNode(const DOMPointInit& aPoint,
const GeometryNode& aFrom,
const ConvertCoordinateOptions& aOptions,
+ CallerType aCallerType,
ErrorResult& aRv)
{
- return mozilla::ConvertPointFromNode(this, aPoint, aFrom, aOptions, aRv);
+ return mozilla::ConvertPointFromNode(this, aPoint, aFrom, aOptions,
+ aCallerType, aRv);
}
nsresult
nsINode::DispatchEvent(nsIDOMEvent *aEvent, bool* aRetVal)
{
// XXX sXBL/XBL2 issue -- do we really want the owner here? What
// if that's the XBL document? Would we want its presshell? Or what?
nsCOMPtr<nsIDocument> document = OwnerDoc();
@@ -2939,17 +2949,17 @@ nsINode::WrapObject(JSContext *aCx, JS::
// (3) we are running a privileged script.
// Event handling is possible only if (1). If (2) event handling is
// prevented.
// If the document has never had a script handling object, untrusted
// scripts (3) shouldn't touch it!
bool hasHadScriptHandlingObject = false;
if (!OwnerDoc()->GetScriptHandlingObject(hasHadScriptHandlingObject) &&
!hasHadScriptHandlingObject &&
- !nsContentUtils::IsCallerChrome()) {
+ !nsContentUtils::IsSystemCaller(aCx)) {
Throw(aCx, NS_ERROR_UNEXPECTED);
return nullptr;
}
JS::Rooted<JSObject*> obj(aCx, WrapNode(aCx, aGivenProto));
MOZ_ASSERT_IF(obj && ChromeOnlyAccess(),
xpc::IsInContentXBLScope(obj) ||
!xpc::UseContentXBLScope(js::GetObjectCompartment(obj)));
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -78,16 +78,17 @@ class Element;
class EventHandlerNonNull;
template<typename T> class Optional;
class OwningNodeOrString;
template<typename> class Sequence;
class Text;
class TextOrElementOrDocument;
struct DOMPointInit;
struct GetRootNodeOptions;
+enum class CallerType : uint32_t;
} // namespace dom
} // namespace mozilla
#define NODE_FLAG_BIT(n_) \
(nsWrapperCache::FlagsType(1U) << (WRAPPER_CACHE_FLAGS_BITS_USED + (n_)))
enum {
// This bit will be set if the node has a listener manager.
@@ -280,16 +281,17 @@ public:
typedef mozilla::dom::BoxQuadOptions BoxQuadOptions;
typedef mozilla::dom::ConvertCoordinateOptions ConvertCoordinateOptions;
typedef mozilla::dom::DOMPoint DOMPoint;
typedef mozilla::dom::DOMPointInit DOMPointInit;
typedef mozilla::dom::DOMQuad DOMQuad;
typedef mozilla::dom::DOMRectReadOnly DOMRectReadOnly;
typedef mozilla::dom::OwningNodeOrString OwningNodeOrString;
typedef mozilla::dom::TextOrElementOrDocument TextOrElementOrDocument;
+ typedef mozilla::dom::CallerType CallerType;
typedef mozilla::ErrorResult ErrorResult;
template<class T>
using Sequence = mozilla::dom::Sequence<T>;
NS_DECLARE_STATIC_IID_ACCESSOR(NS_INODE_IID)
// Among the sub-classes that inherit (directly or indirectly) from nsINode,
@@ -1768,17 +1770,19 @@ public:
aNodeName.SetStringBuffer(nsStringBuffer::FromString(nodeName),
nodeName.Length());
}
MOZ_MUST_USE nsresult GetBaseURI(nsAString& aBaseURI) const;
// Return the base URI for the document.
// The returned value may differ if the document is loaded via XHR, and
// when accessed from chrome privileged script and
// from content privileged script for compatibility.
- void GetBaseURIFromJS(nsAString& aBaseURI, mozilla::ErrorResult& aRv) const;
+ void GetBaseURIFromJS(nsAString& aBaseURI,
+ CallerType aCallerType,
+ ErrorResult& aRv) const;
bool HasChildNodes() const
{
return HasChildren();
}
uint16_t CompareDocumentPosition(nsINode& aOther) const;
void GetNodeValue(nsAString& aNodeValue)
{
GetNodeValueInternal(aNodeValue);
@@ -1873,29 +1877,33 @@ public:
mozilla::dom::Element* GetFirstElementChild() const;
mozilla::dom::Element* GetLastElementChild() const;
void Prepend(const Sequence<OwningNodeOrString>& aNodes, ErrorResult& aRv);
void Append(const Sequence<OwningNodeOrString>& aNodes, ErrorResult& aRv);
void GetBoxQuads(const BoxQuadOptions& aOptions,
nsTArray<RefPtr<DOMQuad> >& aResult,
- mozilla::ErrorResult& aRv);
+ CallerType aCallerType,
+ ErrorResult& aRv);
already_AddRefed<DOMQuad> ConvertQuadFromNode(DOMQuad& aQuad,
const TextOrElementOrDocument& aFrom,
const ConvertCoordinateOptions& aOptions,
+ CallerType aCallerType,
ErrorResult& aRv);
already_AddRefed<DOMQuad> ConvertRectFromNode(DOMRectReadOnly& aRect,
const TextOrElementOrDocument& aFrom,
const ConvertCoordinateOptions& aOptions,
+ CallerType aCallerType,
ErrorResult& aRv);
already_AddRefed<DOMPoint> ConvertPointFromNode(const DOMPointInit& aPoint,
const TextOrElementOrDocument& aFrom,
const ConvertCoordinateOptions& aOptions,
+ CallerType aCallerType,
ErrorResult& aRv);
protected:
// Override this function to create a custom slots class.
// Must not return null.
virtual nsINode::nsSlots* CreateSlots();
--- a/dom/base/nsIObjectLoadingContent.idl
+++ b/dom/base/nsIObjectLoadingContent.idl
@@ -121,21 +121,16 @@ interface nsIObjectLoadingContent : nsIS
*/
[noscript] void pluginDestroyed();
[noscript] void pluginCrashed(in nsIPluginTag pluginTag,
in AString pluginDumpID,
in AString browserDumpID,
in boolean submittedCrashReport);
/**
- * This method will play a plugin that has been stopped by click-to-play.
- */
- void playPlugin();
-
- /**
* Forces a re-evaluation and reload of the tag, optionally invalidating its
* click-to-play state. This can be used when the MIME type that provides a
* type has changed, for instance, to force the tag to re-evalulate the
* handler to use.
*/
void reload(in boolean aClearActivation);
/**
@@ -163,23 +158,9 @@ interface nsIObjectLoadingContent : nsIS
*/
[noscript] void initializeFromChannel(in nsIRequest request);
/**
* The URL of the data/src loaded in the object. This may be null (i.e.
* an <embed> with no src).
*/
readonly attribute nsIURI srcURI;
-
- /**
- * The plugin's current state of fallback content. This property
- * only makes sense if the plugin is not activated.
- */
- readonly attribute unsigned long pluginFallbackType;
-
- /**
- * If this plugin runs out-of-process, it has a runID to differentiate
- * between different times the plugin process has been instantiated.
- *
- * This throws NS_ERROR_NOT_IMPLEMENTED for in-process plugins.
- */
- readonly attribute unsigned long runID;
};
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -2910,16 +2910,20 @@ nsresult
nsObjectLoadingContent::ScriptRequestPluginInstance(JSContext* aCx,
nsNPAPIPluginInstance **aResult)
{
// The below methods pull the cx off the stack, so make sure they match.
//
// NB: Sometimes there's a null cx on the stack, in which case |cx| is the
// safe JS context. But in that case, IsCallerChrome() will return true,
// so the ensuing expression is short-circuited.
+ // XXXbz the NB comment above doesn't really make sense. At the moment, all
+ // the callers to this except maybe SetupProtoChain have a useful JSContext*
+ // that could be used for nsContentUtils::IsSystemCaller... We do need to
+ // sort out what the SetupProtoChain callers look like.
MOZ_ASSERT_IF(nsContentUtils::GetCurrentJSContext(),
aCx == nsContentUtils::GetCurrentJSContext());
bool callerIsContentJS = (nsContentUtils::GetCurrentJSContext() &&
!nsContentUtils::IsCallerChrome() &&
!nsContentUtils::IsCallerContentXBL());
nsCOMPtr<nsIContent> thisContent =
do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
@@ -3162,35 +3166,31 @@ nsObjectLoadingContent::NotifyContentObj
// Nothing to do here if there's no wrapper for mContent. The proto
// chain will be fixed appropriately when the wrapper is created.
return;
}
SetupProtoChain(cx, obj);
}
-NS_IMETHODIMP
-nsObjectLoadingContent::PlayPlugin()
+void
+nsObjectLoadingContent::PlayPlugin(SystemCallerGuarantee, ErrorResult& aRv)
{
- if (!nsContentUtils::IsCallerChrome())
- return NS_OK;
-
+ // This is a ChromeOnly method, so no need to check caller type here.
if (!mActivated) {
mActivated = true;
LOG(("OBJLC [%p]: Activated by user", this));
}
// If we're in a click-to-play state, reload.
// Fallback types >= eFallbackClickToPlay are plugin-replacement types, see
// header
if (mType == eType_Null && mFallbackType >= eFallbackClickToPlay) {
- return LoadObject(true, true);
+ aRv = LoadObject(true, true);
}
-
- return NS_OK;
}
NS_IMETHODIMP
nsObjectLoadingContent::Reload(bool aClearActivation)
{
if (aClearActivation) {
mActivated = false;
}
@@ -3200,51 +3200,37 @@ nsObjectLoadingContent::Reload(bool aCle
NS_IMETHODIMP
nsObjectLoadingContent::GetActivated(bool *aActivated)
{
*aActivated = Activated();
return NS_OK;
}
-NS_IMETHODIMP
-nsObjectLoadingContent::GetPluginFallbackType(uint32_t* aPluginFallbackType)
-{
- NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
- *aPluginFallbackType = mFallbackType;
- return NS_OK;
-}
-
uint32_t
nsObjectLoadingContent::DefaultFallbackType()
{
FallbackType reason;
bool go = ShouldPlay(reason, true);
if (go) {
return PLUGIN_ACTIVE;
}
return reason;
}
-NS_IMETHODIMP
-nsObjectLoadingContent::GetRunID(uint32_t* aRunID)
+uint32_t
+nsObjectLoadingContent::GetRunID(SystemCallerGuarantee, ErrorResult& aRv)
{
- if (NS_WARN_IF(!nsContentUtils::IsCallerChrome())) {
- return NS_ERROR_NOT_AVAILABLE;
- }
- if (NS_WARN_IF(!aRunID)) {
- return NS_ERROR_INVALID_POINTER;
- }
if (!mHasRunID) {
// The plugin instance must not have a run ID, so we must
// be running the plugin in-process.
- return NS_ERROR_NOT_IMPLEMENTED;
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return 0;
}
- *aRunID = mRunID;
- return NS_OK;
+ return mRunID;
}
static bool sPrefsInitialized;
static uint32_t sSessionTimeoutMinutes;
static uint32_t sPersistentTimeoutDays;
bool
nsObjectLoadingContent::ShouldBlockContent()
--- a/dom/base/nsObjectLoadingContent.h
+++ b/dom/base/nsObjectLoadingContent.h
@@ -9,16 +9,17 @@
* various content nodes that want to provide plugin/document/image
* loading functionality (eg <embed>, <object>, <applet>, etc).
*/
#ifndef NSOBJECTLOADINGCONTENT_H_
#define NSOBJECTLOADINGCONTENT_H_
#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
#include "nsImageLoadingContent.h"
#include "nsIStreamListener.h"
#include "nsIChannelEventSink.h"
#include "nsIContentPolicy.h"
#include "nsIObjectLoadingContent.h"
#include "nsIRunnable.h"
#include "nsIThreadInternal.h"
#include "nsIFrame.h"
@@ -188,20 +189,18 @@ class nsObjectLoadingContent : public ns
uint32_t DisplayedType() const
{
return mType;
}
uint32_t GetContentTypeForMIMEType(const nsAString& aMIMEType)
{
return GetTypeOfContent(NS_ConvertUTF16toUTF8(aMIMEType));
}
- void PlayPlugin(mozilla::ErrorResult& aRv)
- {
- aRv = PlayPlugin();
- }
+ void PlayPlugin(mozilla::dom::SystemCallerGuarantee,
+ mozilla::ErrorResult& aRv);
void Reload(bool aClearActivation, mozilla::ErrorResult& aRv)
{
aRv = Reload(aClearActivation);
}
bool Activated() const
{
return mActivated;
}
@@ -234,27 +233,18 @@ class nsObjectLoadingContent : public ns
{
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
}
void LegacyCall(JSContext* aCx, JS::Handle<JS::Value> aThisVal,
const mozilla::dom::Sequence<JS::Value>& aArguments,
JS::MutableHandle<JS::Value> aRetval,
mozilla::ErrorResult& aRv);
- uint32_t GetRunID(mozilla::ErrorResult& aRv)
- {
- uint32_t runID;
- nsresult rv = GetRunID(&runID);
- if (NS_FAILED(rv)) {
- aRv.Throw(rv);
- return 0;
- }
-
- return runID;
- }
+ uint32_t GetRunID(mozilla::dom::SystemCallerGuarantee,
+ mozilla::ErrorResult& aRv);
bool IsRewrittenYoutubeEmbed() const
{
return mRewrittenYoutubeEmbed;
}
void PresetOpenerWindow(mozIDOMWindowProxy* aOpenerWindow, mozilla::ErrorResult& aRv);
--- a/dom/bindings/BindingDeclarations.h
+++ b/dom/bindings/BindingDeclarations.h
@@ -530,12 +530,20 @@ public:
} // namespace binding_detail
// Enum to represent a system or non-system caller type.
enum class CallerType : uint32_t {
System,
NonSystem
};
+// A class that can be passed (by value or const reference) to indicate that the
+// caller is always a system caller. This can be used as the type of an
+// argument to force only system callers to call a function.
+class SystemCallerGuarantee {
+public:
+ operator CallerType() const { return CallerType::System; }
+};
+
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_BindingDeclarations_h__
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1148,32 +1148,32 @@ QueryInterface(JSContext* cx, unsigned a
if (!obj) {
JS_ReportErrorASCII(cx, "Permission denied to access object");
return false;
}
// Switch this to UnwrapDOMObjectToISupports once our global objects are
// using new bindings.
nsCOMPtr<nsISupports> native;
- UnwrapArg<nsISupports>(obj, getter_AddRefs(native));
+ UnwrapArg<nsISupports>(cx, obj, getter_AddRefs(native));
if (!native) {
return Throw(cx, NS_ERROR_FAILURE);
}
if (argc < 1) {
return Throw(cx, NS_ERROR_XPC_NOT_ENOUGH_ARGS);
}
if (!args[0].isObject()) {
return Throw(cx, NS_ERROR_XPC_BAD_CONVERT_JS);
}
nsCOMPtr<nsIJSID> iid;
obj = &args[0].toObject();
- if (NS_FAILED(UnwrapArg<nsIJSID>(obj, getter_AddRefs(iid)))) {
+ if (NS_FAILED(UnwrapArg<nsIJSID>(cx, obj, getter_AddRefs(iid)))) {
return Throw(cx, NS_ERROR_XPC_BAD_CONVERT_JS);
}
MOZ_ASSERT(iid);
if (iid->GetID()->Equals(NS_GET_IID(nsIClassInfo))) {
nsresult rv;
nsCOMPtr<nsIClassInfo> ci = do_QueryInterface(native, &rv);
if (NS_FAILED(rv)) {
@@ -3149,17 +3149,18 @@ AssertReturnTypeMatchesJitinfo(const JSJ
bool
CallerSubsumes(JSObject *aObject)
{
nsIPrincipal* objPrin = nsContentUtils::ObjectPrincipal(js::UncheckedUnwrap(aObject));
return nsContentUtils::SubjectPrincipal()->Subsumes(objPrin);
}
nsresult
-UnwrapArgImpl(JS::Handle<JSObject*> src,
+UnwrapArgImpl(JSContext* cx,
+ JS::Handle<JSObject*> src,
const nsIID &iid,
void **ppArg)
{
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_AVAILABLE;
}
nsISupports *iface = xpc::UnwrapReflectorToISupports(src);
@@ -3169,17 +3170,17 @@ UnwrapArgImpl(JS::Handle<JSObject*> src,
}
return NS_OK;
}
// Only allow XPCWrappedJS stuff in system code. Ideally we would remove this
// even there, but that involves converting some things to WebIDL callback
// interfaces and making some other things builtinclass...
- if (!nsContentUtils::IsCallerChrome()) {
+ if (!nsContentUtils::IsSystemCaller(cx)) {
return NS_ERROR_XPC_BAD_CONVERT_JS;
}
RefPtr<nsXPCWrappedJS> wrappedJS;
nsresult rv = nsXPCWrappedJS::GetNewOrUsed(src, iid, getter_AddRefs(wrappedJS));
if (NS_FAILED(rv) || !wrappedJS) {
return rv;
}
@@ -3187,21 +3188,22 @@ UnwrapArgImpl(JS::Handle<JSObject*> src,
// We need to go through the QueryInterface logic to make this return
// the right thing for the various 'special' interfaces; e.g.
// nsIPropertyBag. We must use AggregatedQueryInterface in cases where
// there is an outer to avoid nasty recursion.
return wrappedJS->QueryInterface(iid, ppArg);
}
nsresult
-UnwrapWindowProxyImpl(JS::Handle<JSObject*> src,
+UnwrapWindowProxyImpl(JSContext* cx,
+ JS::Handle<JSObject*> src,
nsPIDOMWindowOuter** ppArg)
{
nsCOMPtr<nsPIDOMWindowInner> inner;
- nsresult rv = UnwrapArg<nsPIDOMWindowInner>(src, getter_AddRefs(inner));
+ nsresult rv = UnwrapArg<nsPIDOMWindowInner>(cx, src, getter_AddRefs(inner));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsPIDOMWindowOuter> outer = inner->GetOuterWindow();
outer.forget(ppArg);
return NS_OK;
}
bool
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -48,35 +48,38 @@ class nsIJSID;
namespace mozilla {
enum UseCounter : int16_t;
namespace dom {
template<typename DataType> class MozMap;
nsresult
-UnwrapArgImpl(JS::Handle<JSObject*> src, const nsIID& iid, void** ppArg);
+UnwrapArgImpl(JSContext* cx, JS::Handle<JSObject*> src, const nsIID& iid,
+ void** ppArg);
nsresult
-UnwrapWindowProxyImpl(JS::Handle<JSObject*> src, nsPIDOMWindowOuter** ppArg);
+UnwrapWindowProxyImpl(JSContext* cx, JS::Handle<JSObject*> src,
+ nsPIDOMWindowOuter** ppArg);
/** Convert a jsval to an XPCOM pointer. */
template <class Interface>
inline nsresult
-UnwrapArg(JS::Handle<JSObject*> src, Interface** ppArg)
+UnwrapArg(JSContext* cx, JS::Handle<JSObject*> src, Interface** ppArg)
{
- return UnwrapArgImpl(src, NS_GET_TEMPLATE_IID(Interface),
+ return UnwrapArgImpl(cx, src, NS_GET_TEMPLATE_IID(Interface),
reinterpret_cast<void**>(ppArg));
}
template <>
inline nsresult
-UnwrapArg<nsPIDOMWindowOuter>(JS::Handle<JSObject*> src, nsPIDOMWindowOuter** ppArg)
+UnwrapArg<nsPIDOMWindowOuter>(JSContext* cx, JS::Handle<JSObject*> src,
+ nsPIDOMWindowOuter** ppArg)
{
- return UnwrapWindowProxyImpl(src, ppArg);
+ return UnwrapWindowProxyImpl(cx, src, ppArg);
}
bool
ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
bool aSecurityError, const char* aInterfaceName);
bool
ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -691,19 +691,16 @@ DOMInterfaces = {
'NetworkInformation': {
'nativeType': 'mozilla::dom::network::Connection',
},
'Node': {
'nativeType': 'nsINode',
'concrete': False,
- 'binaryNames': {
- 'baseURI': 'baseURIFromJS'
- }
},
'NodeIterator': {
'wrapperCache': False,
},
'NodeList': {
'nativeType': 'nsINodeList',
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -4238,25 +4238,25 @@ class CastableObjectUnwrapper():
codeOnFailure=(codeOnFailure % { 'securityError': 'true'}))
self.substitution["source"] = "maybeUncheckedObj"
xpconnectUnwrap = dedent("""
nsresult rv;
{ // Scope for the JSAutoCompartment, because we only
// want to be in that compartment for the UnwrapArg call.
JS::Rooted<JSObject*> source(cx, ${source});
JSAutoCompartment ac(cx, ${source});
- rv = UnwrapArg<${type}>(source, getter_AddRefs(objPtr));
+ rv = UnwrapArg<${type}>(cx, source, getter_AddRefs(objPtr));
}
""")
else:
self.substitution["uncheckedObjDecl"] = ""
self.substitution["source"] = source
xpconnectUnwrap = (
"JS::Rooted<JSObject*> source(cx, ${source});\n"
- "nsresult rv = UnwrapArg<${type}>(source, getter_AddRefs(objPtr));\n")
+ "nsresult rv = UnwrapArg<${type}>(cx, source, getter_AddRefs(objPtr));\n")
if descriptor.hasXPConnectImpls:
self.substitution["codeOnFailure"] = string.Template(
"RefPtr<${type}> objPtr;\n" +
xpconnectUnwrap +
"if (NS_FAILED(rv)) {\n"
"${indentedCodeOnFailure}"
"}\n"
@@ -5498,17 +5498,17 @@ def getJSToNativeConversionInfo(type, de
if forceOwningType:
# Don't return a holderType in this case; our declName
# will just own stuff.
templateBody += "RefPtr<" + typeName + "> ${holderName};\n"
else:
holderType = "RefPtr<" + typeName + ">"
templateBody += (
"JS::Rooted<JSObject*> source(cx, &${val}.toObject());\n" +
- "if (NS_FAILED(UnwrapArg<" + typeName + ">(source, getter_AddRefs(${holderName})))) {\n")
+ "if (NS_FAILED(UnwrapArg<" + typeName + ">(cx, source, getter_AddRefs(${holderName})))) {\n")
templateBody += CGIndenter(onFailureBadType(failureCode,
descriptor.interface.identifier.name)).define()
templateBody += ("}\n"
"MOZ_ASSERT(${holderName});\n")
# And store our value in ${declName}
templateBody += "${declName} = ${holderName};\n"
@@ -7034,16 +7034,17 @@ class CGCallGenerator(CGThing):
isFallible is a boolean indicating whether the call should be fallible.
resultVar: If the returnType is not void, then the result of the call is
stored in a C++ variable named by resultVar. The caller is responsible for
declaring the result variable. If the caller doesn't care about the result
value, resultVar can be omitted.
"""
def __init__(self, isFallible, needsSubjectPrincipal, needsCallerType,
+ isChromeOnly,
arguments, argsPre, returnType, extendedAttributes, descriptor,
nativeMethodName, static, object="self", argsPost=[],
resultVar=None):
CGThing.__init__(self)
result, resultOutParam, resultRooter, resultArgs, resultConversion = \
getRetvalDeclarationForType(returnType, descriptor)
@@ -7099,17 +7100,20 @@ class CGCallGenerator(CGThing):
else:
assert resultOutParam == "ptr"
args.append(CGGeneric("&" + resultVar))
if needsSubjectPrincipal:
args.append(CGGeneric("subjectPrincipal"))
if needsCallerType:
- args.append(CGGeneric(callerTypeGetterForDescriptor(descriptor)))
+ if isChromeOnly:
+ args.append(CGGeneric("SystemCallerGuarantee()"))
+ else:
+ args.append(CGGeneric(callerTypeGetterForDescriptor(descriptor)))
canOOM = "canOOM" in extendedAttributes
if isFallible or canOOM:
args.append(CGGeneric("rv"))
args.extend(CGGeneric(arg) for arg in argsPost)
# Build up our actual call
self.cgRoot = CGList([])
@@ -7596,16 +7600,17 @@ class CGPerSignatureCall(CGThing):
cgThings.append(CGIterableMethodGenerator(descriptor,
idlNode.maplikeOrSetlikeOrIterable,
idlNode.identifier.name))
else:
cgThings.append(CGCallGenerator(
self.isFallible(),
idlNode.getExtendedAttribute('NeedsSubjectPrincipal'),
needsCallerType(idlNode),
+ isChromeOnly(idlNode),
self.getArguments(), argsPre, returnType,
self.extendedAttributes, descriptor,
nativeMethodName,
static, argsPost=argsPost, resultVar=resultVar))
if useCounterName:
# Generate a telemetry call for when [UseCounter] is used.
code = "SetDocumentAndPageUseCounter(cx, obj, eUseCounter_%s);\n" % useCounterName
--- a/dom/bindings/test/TestFunctions.cpp
+++ b/dom/bindings/test/TestFunctions.cpp
@@ -1,20 +1,27 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/TestFunctions.h"
#include "mozilla/dom/TestFunctionsBinding.h"
+#include "nsStringBuffer.h"
namespace mozilla {
namespace dom {
+/* static */ TestFunctions*
+TestFunctions::Constructor(GlobalObject& aGlobal, ErrorResult& aRv)
+{
+ return new TestFunctions;
+}
+
/* static */ void
TestFunctions::ThrowUncatchableException(GlobalObject& aGlobal,
ErrorResult& aRv)
{
aRv.ThrowUncatchableException();
}
/* static */ Promise*
@@ -26,10 +33,62 @@ TestFunctions::PassThroughPromise(Global
/* static */ already_AddRefed<Promise>
TestFunctions::PassThroughCallbackPromise(GlobalObject& aGlobal,
PromiseReturner& aCallback,
ErrorResult& aRv)
{
return aCallback.Call(aRv);
}
+void
+TestFunctions::SetStringData(const nsAString& aString)
+{
+ mStringData = aString;
+}
+
+void
+TestFunctions::GetStringDataAsAString(nsAString& aString)
+{
+ aString = mStringData;
+}
+
+void
+TestFunctions::GetStringDataAsAString(uint32_t aLength, nsAString& aString)
+{
+ MOZ_RELEASE_ASSERT(aLength <= mStringData.Length(),
+ "Bogus test passing in a too-big length");
+ aString.Assign(mStringData.BeginReading(), aLength);
+}
+
+void
+TestFunctions::GetStringDataAsDOMString(const Optional<uint32_t>& aLength,
+ DOMString& aString)
+{
+ uint32_t length;
+ if (aLength.WasPassed()) {
+ length = aLength.Value();
+ MOZ_RELEASE_ASSERT(length <= mStringData.Length(),
+ "Bogus test passing in a too-big length");
+ } else {
+ length = mStringData.Length();
+ }
+
+ nsStringBuffer* buf = nsStringBuffer::FromString(mStringData);
+ if (buf) {
+ aString.SetStringBuffer(buf, length);
+ return;
+ }
+
+ // We better have an empty mStringData; otherwise why did we not have a string
+ // buffer?
+ MOZ_RELEASE_ASSERT(length == 0, "Why no stringbuffer?");
+ // No need to do anything here; aString is already empty.
+}
+
+bool
+TestFunctions::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aWrapper)
+{
+ return TestFunctionsBinding::Wrap(aCx, this, aGivenProto, aWrapper);
+}
+
}
}
--- a/dom/bindings/test/TestFunctions.h
+++ b/dom/bindings/test/TestFunctions.h
@@ -5,33 +5,48 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_TestFunctions_h
#define mozilla_dom_TestFunctions_h
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/NonRefcountedDOMObject.h"
+#include "nsString.h"
namespace mozilla {
namespace dom {
class Promise;
class PromiseReturner;
class TestFunctions : public NonRefcountedDOMObject {
public:
+ static TestFunctions* Constructor(GlobalObject& aGlobal, ErrorResult& aRv);
+
static void
ThrowUncatchableException(GlobalObject& aGlobal, ErrorResult& aRv);
static Promise*
PassThroughPromise(GlobalObject& aGlobal, Promise& aPromise);
static already_AddRefed<Promise>
PassThroughCallbackPromise(GlobalObject& aGlobal,
PromiseReturner& aCallback,
ErrorResult& aRv);
+
+ void SetStringData(const nsAString& aString);
+
+ void GetStringDataAsAString(nsAString& aString);
+ void GetStringDataAsAString(uint32_t aLength, nsAString& aString);
+ void GetStringDataAsDOMString(const Optional<uint32_t>& aLength,
+ DOMString& aString);
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aWrapper);
+private:
+ nsString mStringData;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_TestFunctions_h
--- a/dom/bindings/test/mochitest.ini
+++ b/dom/bindings/test/mochitest.ini
@@ -71,8 +71,10 @@ skip-if = debug == false
[test_jsimplemented_eventhandler.html]
skip-if = debug == false
[test_iterable.html]
skip-if = debug == false
[test_oom_reporting.html]
[test_domProxyArrayLengthGetter.html]
[test_exceptionSanitization.html]
skip-if = os == "android"
+[test_stringBindings.html]
+skip-if = debug == false
new file mode 100644
--- /dev/null
+++ b/dom/bindings/test/test_stringBindings.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1334537
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1334537</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1334537 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function go() {
+ // Need a new global that will pick up our pref.
+ var ifr = document.createElement("iframe");
+ document.body.appendChild(ifr);
+
+ var t = new ifr.contentWindow.TestFunctions();
+ var testString = "abcdefghijklmnopqrstuvwxyz";
+ const substringLength = 10;
+ var shortTestString = testString.substring(0, substringLength);
+
+ t.setStringData(testString);
+ // Note: we want to do all our gets before we start running code we don't
+ // control inside the test harness, if we really want to exercise the string
+ // cache in controlled ways.
+
+ var asShortDOMString = t.getStringDataAsDOMString(substringLength);
+ var asFullDOMString = t.getStringDataAsDOMString();
+ var asShortAString = t.getStringDataAsAString(substringLength);
+ var asAString = t.getStringDataAsAString();
+
+ is(asShortAString, shortTestString, "Short DOMString should be short");
+ is(asFullDOMString, testString, "Full DOMString should be test string");
+ is(asShortAString, shortTestString, "Short AString should be short");
+ is(asAString, testString, "Full AString should be test string");
+
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]},
+ go);
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1334537">Mozilla Bug 1334537</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/cache/Cache.cpp
+++ b/dom/cache/Cache.cpp
@@ -320,17 +320,17 @@ Cache::MatchAll(const Optional<RequestOr
}
}
return ExecuteOp(args, aRv);
}
already_AddRefed<Promise>
Cache::Add(JSContext* aContext, const RequestOrUSVString& aRequest,
- ErrorResult& aRv)
+ CallerType aCallerType, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
CacheChild::AutoLock actorLock(mActor);
@@ -350,22 +350,23 @@ Cache::Add(JSContext* aContext, const Re
nsAutoString url;
request->GetUrl(url);
if (NS_WARN_IF(!IsValidPutRequestURL(url, aRv))) {
return nullptr;
}
requestList.AppendElement(Move(request));
- return AddAll(global, Move(requestList), aRv);
+ return AddAll(global, Move(requestList), aCallerType, aRv);
}
already_AddRefed<Promise>
Cache::AddAll(JSContext* aContext,
const Sequence<OwningRequestOrUSVString>& aRequestList,
+ CallerType aCallerType,
ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
CacheChild::AutoLock actorLock(mActor);
@@ -399,17 +400,17 @@ Cache::AddAll(JSContext* aContext,
request->GetUrl(url);
if (NS_WARN_IF(!IsValidPutRequestURL(url, aRv))) {
return nullptr;
}
requestList.AppendElement(Move(request));
}
- return AddAll(global, Move(requestList), aRv);
+ return AddAll(global, Move(requestList), aCallerType, aRv);
}
already_AddRefed<Promise>
Cache::Put(const RequestOrUSVString& aRequest, Response& aResponse,
ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
@@ -591,17 +592,18 @@ Cache::ExecuteOp(AutoChildOpArgs& aOpArg
}
mActor->ExecuteOp(mGlobal, promise, this, aOpArgs.SendAsOpArgs());
return promise.forget();
}
already_AddRefed<Promise>
Cache::AddAll(const GlobalObject& aGlobal,
- nsTArray<RefPtr<Request>>&& aRequestList, ErrorResult& aRv)
+ nsTArray<RefPtr<Request>>&& aRequestList,
+ CallerType aCallerType, ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(mActor);
// If there is no work to do, then resolve immediately
if (aRequestList.IsEmpty()) {
RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
if (NS_WARN_IF(!promise)) {
return nullptr;
@@ -617,17 +619,17 @@ Cache::AddAll(const GlobalObject& aGloba
// Begin fetching each request in parallel. For now, if an error occurs just
// abandon our previous fetch calls. In theory we could cancel them in the
// future once fetch supports it.
for (uint32_t i = 0; i < aRequestList.Length(); ++i) {
RequestOrUSVString requestOrString;
requestOrString.SetAsRequest() = aRequestList[i];
RefPtr<Promise> fetch = FetchRequest(mGlobal, requestOrString,
- RequestInit(), aRv);
+ RequestInit(), aCallerType, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
fetchList.AppendElement(Move(fetch));
}
RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
--- a/dom/cache/Cache.h
+++ b/dom/cache/Cache.h
@@ -24,16 +24,17 @@ namespace dom {
class OwningRequestOrUSVString;
class Promise;
struct CacheQueryOptions;
class RequestOrUSVString;
class Response;
template<typename T> class Optional;
template<typename T> class Sequence;
+enum class CallerType : uint32_t;
namespace cache {
class AutoChildOpArgs;
class CacheChild;
class Cache final : public nsISupports
, public nsWrapperCache
@@ -46,20 +47,21 @@ public:
already_AddRefed<Promise>
Match(const RequestOrUSVString& aRequest, const CacheQueryOptions& aOptions,
ErrorResult& aRv);
already_AddRefed<Promise>
MatchAll(const Optional<RequestOrUSVString>& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv);
already_AddRefed<Promise>
Add(JSContext* aContext, const RequestOrUSVString& aRequest,
- ErrorResult& aRv);
+ CallerType aCallerType, ErrorResult& aRv);
already_AddRefed<Promise>
AddAll(JSContext* aContext,
- const Sequence<OwningRequestOrUSVString>& aRequests, ErrorResult& aRv);
+ const Sequence<OwningRequestOrUSVString>& aRequests,
+ CallerType aCallerType, ErrorResult& aRv);
already_AddRefed<Promise>
Put(const RequestOrUSVString& aRequest, Response& aResponse,
ErrorResult& aRv);
already_AddRefed<Promise>
Delete(const RequestOrUSVString& aRequest, const CacheQueryOptions& aOptions,
ErrorResult& aRv);
already_AddRefed<Promise>
Keys(const Optional<RequestOrUSVString>& aRequest,
@@ -93,17 +95,17 @@ private:
// Called when we're destroyed or CCed.
void DisconnectFromActor();
already_AddRefed<Promise>
ExecuteOp(AutoChildOpArgs& aOpArgs, ErrorResult& aRv);
already_AddRefed<Promise>
AddAll(const GlobalObject& aGlobal, nsTArray<RefPtr<Request>>&& aRequestList,
- ErrorResult& aRv);
+ CallerType aCallerType, ErrorResult& aRv);
already_AddRefed<Promise>
PutAll(const nsTArray<RefPtr<Request>>& aRequestList,
const nsTArray<RefPtr<Response>>& aResponseList,
ErrorResult& aRv);
nsCOMPtr<nsIGlobalObject> mGlobal;
CacheChild* mActor;
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -5351,108 +5351,16 @@ CanvasRenderingContext2D::DrawWindow(nsG
}
// note that x and y are coordinates in the document that
// we're drawing; x and y are drawn to 0,0 in current user
// space.
RedrawUser(gfxRect(0, 0, aW, aH));
}
-void
-CanvasRenderingContext2D::AsyncDrawXULElement(nsXULElement& aElem,
- double aX, double aY,
- double aW, double aH,
- const nsAString& aBgColor,
- uint32_t aFlags,
- ErrorResult& aError)
-{
- // We can't allow web apps to call this until we fix at least the
- // following potential security issues:
- // -- rendering cross-domain IFRAMEs and then extracting the results
- // -- rendering the user's theme and then extracting the results
- // -- rendering native anonymous content (e.g., file input paths;
- // scrollbars should be allowed)
- if (!nsContentUtils::IsCallerChrome()) {
- // not permitted to use DrawWindow
- // XXX ERRMSG we need to report an error to developers here! (bug 329026)
- aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
- return;
- }
-
-#if 0
- nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(&elem);
- if (!loaderOwner) {
- aError.Throw(NS_ERROR_FAILURE);
- return;
- }
-
- RefPtr<nsFrameLoader> frameloader = loaderOwner->GetFrameLoader();
- if (!frameloader) {
- aError.Throw(NS_ERROR_FAILURE);
- return;
- }
-
- PBrowserParent *child = frameloader->GetRemoteBrowser();
- if (!child) {
- nsIDocShell* docShell = frameLoader->GetExistingDocShell();
- if (!docShell) {
- aError.Throw(NS_ERROR_FAILURE);
- return;
- }
-
- nsCOMPtr<nsPIDOMWindowOuter> window = docShell->GetWindow();
- if (!window) {
- aError.Throw(NS_ERROR_FAILURE);
- return;
- }
-
- return DrawWindow(window->GetCurrentInnerWindow(), aX, aY, aW, aH,
- aBgColor, aFlags);
- }
-
- // protect against too-large surfaces that will cause allocation
- // or overflow issues
- if (!Factory::CheckSurfaceSize(IntSize(aW, aH), 0xffff)) {
- aError.Throw(NS_ERROR_FAILURE);
- return;
- }
-
- bool flush =
- (aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DO_NOT_FLUSH) == 0;
-
- uint32_t renderDocFlags = nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING;
- if (aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_CARET) {
- renderDocFlags |= nsIPresShell::RENDER_CARET;
- }
- if (aFlags & nsIDOMCanvasRenderingContext2D::DRAWWINDOW_DRAW_VIEW) {
- renderDocFlags &= ~nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING;
- }
-
- nsRect rect(nsPresContext::CSSPixelsToAppUnits(aX),
- nsPresContext::CSSPixelsToAppUnits(aY),
- nsPresContext::CSSPixelsToAppUnits(aW),
- nsPresContext::CSSPixelsToAppUnits(aH));
- if (mIPC) {
- PDocumentRendererParent *pdocrender =
- child->SendPDocumentRendererConstructor(rect,
- mThebes->CurrentMatrix(),
- nsString(aBGColor),
- renderDocFlags, flush,
- nsIntSize(mWidth, mHeight));
- if (!pdocrender)
- return NS_ERROR_FAILURE;
-
- DocumentRendererParent *docrender =
- static_cast<DocumentRendererParent *>(pdocrender);
-
- docrender->SetCanvasContext(this, mThebes);
- }
-#endif
-}
-
//
// device pixel getting/setting
//
already_AddRefed<ImageData>
CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx,
double aSy, double aSw,
double aSh, ErrorResult& aError)
@@ -5465,17 +5373,21 @@ CanvasRenderingContext2D::GetImageData(J
NS_ERROR("No canvas element and no docshell in GetImageData!!!");
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
// Check only if we have a canvas element; if we were created with a docshell,
// then it's special internal use.
if (mCanvasElement && mCanvasElement->IsWriteOnly() &&
- !nsContentUtils::IsCallerChrome())
+ // We could ask bindings for the caller type, but they already hand us a
+ // JSContext, and we're at least _somewhat_ perf-sensitive (so may not
+ // want to compute the caller type in the common non-write-only case), so
+ // let's just use what we have.
+ !nsContentUtils::IsSystemCaller(aCx))
{
// XXX ERRMSG we need to report an error to developers here! (bug 329026)
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
if (!IsFinite(aSx) || !IsFinite(aSy) ||
!IsFinite(aSw) || !IsFinite(aSh)) {
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -395,19 +395,16 @@ public:
CurrentState().imageSmoothingEnabled = aImageSmoothingEnabled;
}
}
void DrawWindow(nsGlobalWindow& aWindow, double aX, double aY,
double aW, double aH,
const nsAString& aBgColor, uint32_t aFlags,
mozilla::ErrorResult& aError);
- void AsyncDrawXULElement(nsXULElement& aElem, double aX, double aY, double aW,
- double aH, const nsAString& aBgColor, uint32_t aFlags,
- mozilla::ErrorResult& aError);
enum RenderingMode {
SoftwareBackendMode,
OpenGLBackendMode,
DefaultBackendMode
};
bool SwitchRenderingMode(RenderingMode aRenderingMode);
--- a/dom/canvas/CanvasRenderingContextHelper.cpp
+++ b/dom/canvas/CanvasRenderingContextHelper.cpp
@@ -48,28 +48,38 @@ CanvasRenderingContextHelper::ToBlob(JSC
AutoJSAPI jsapi;
if (jsapi.Init(mGlobal)) {
JS_updateMallocCounter(jsapi.cx(), size);
}
}
RefPtr<Blob> newBlob = Blob::Create(mGlobal, blob->Impl());
- mBlobCallback->Call(*newBlob, rv);
+ mBlobCallback->Call(newBlob, rv);
mGlobal = nullptr;
mBlobCallback = nullptr;
return rv.StealNSResult();
}
nsCOMPtr<nsIGlobalObject> mGlobal;
RefPtr<BlobCallback> mBlobCallback;
};
+ nsIntSize elemSize = GetWidthHeight();
+ if (elemSize.width == 0 || elemSize.height == 0) {
+ // According to spec, blob should return null if either its horizontal
+ // dimension or its vertical dimension is zero. See link below.
+ // https://html.spec.whatwg.org/multipage/scripting.html#dom-canvas-toblob
+ BlobCallback* blobCallback = &aCallback;
+ blobCallback->Call(nullptr, aRv);
+ return;
+ }
+
RefPtr<EncodeCompleteCallback> callback =
new EncodeCallback(aGlobal, &aCallback);
ToBlob(aCx, aGlobal, callback, aType, aParams, aRv);
}
void
CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -665,42 +665,45 @@ public:
bool StartVRPresentation();
////
webgl::PackingInfo
ValidImplementationColorReadPI(const webgl::FormatUsageInfo* usage) const;
protected:
- bool ReadPixels_SharedPrecheck(ErrorResult* const out_error);
+ bool ReadPixels_SharedPrecheck(dom::CallerType aCallerType,
+ ErrorResult& out_error);
void ReadPixelsImpl(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
GLenum type, void* data, uint32_t dataLen);
bool DoReadPixelsAndConvert(const webgl::FormatInfo* srcFormat, GLint x, GLint y,
GLsizei width, GLsizei height, GLenum format,
GLenum destType, void* dest, uint32_t dataLen,
uint32_t rowStride);
public:
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
GLenum type, const dom::Nullable<dom::ArrayBufferView>& maybeView,
- ErrorResult& rv)
+ dom::CallerType aCallerType, ErrorResult& rv)
{
const char funcName[] = "readPixels";
if (maybeView.IsNull()) {
ErrorInvalidValue("%s: `pixels` must not be null.", funcName);
return;
}
- ReadPixels(x, y, width, height, format, type, maybeView.Value(), 0, rv);
+ ReadPixels(x, y, width, height, format, type, maybeView.Value(), 0,
+ aCallerType, rv);
}
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
- GLenum type, WebGLsizeiptr offset, ErrorResult& out_error);
+ GLenum type, WebGLsizeiptr offset,
+ dom::CallerType, ErrorResult& out_error);
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
GLenum type, const dom::ArrayBufferView& dstData, GLuint dstOffset,
- ErrorResult& out_error);
+ dom::CallerType, ErrorResult& out_error);
////
void RenderbufferStorage(GLenum target, GLenum internalFormat,
GLsizei width, GLsizei height);
protected:
void RenderbufferStorage_base(const char* funcName, GLenum target,
GLsizei samples, GLenum internalformat,
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -1238,27 +1238,28 @@ GetJSScalarFromGLType(GLenum type, js::S
return true;
default:
return false;
}
}
bool
-WebGLContext::ReadPixels_SharedPrecheck(ErrorResult* const out_error)
+WebGLContext::ReadPixels_SharedPrecheck(CallerType aCallerType,
+ ErrorResult& out_error)
{
if (IsContextLost())
return false;
if (mCanvasElement &&
mCanvasElement->IsWriteOnly() &&
- !nsContentUtils::IsCallerChrome())
+ aCallerType != CallerType::System)
{
GenerateWarning("readPixels: Not allowed");
- out_error->Throw(NS_ERROR_DOM_SECURITY_ERR);
+ out_error.Throw(NS_ERROR_DOM_SECURITY_ERR);
return false;
}
return true;
}
bool
WebGLContext::ValidatePackSize(const char* funcName, uint32_t width, uint32_t height,
@@ -1301,20 +1302,21 @@ WebGLContext::ValidatePackSize(const cha
*out_rowStride = rowStride.value();
*out_endOffset = usedBytesPerImage.value();
return true;
}
void
WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
GLenum type, const dom::ArrayBufferView& dstView,
- GLuint dstElemOffset, ErrorResult& out_error)
+ GLuint dstElemOffset, CallerType aCallerType,
+ ErrorResult& out_error)
{
const char funcName[] = "readPixels";
- if (!ReadPixels_SharedPrecheck(&out_error))
+ if (!ReadPixels_SharedPrecheck(aCallerType, out_error))
return;
if (mBoundPixelPackBuffer) {
ErrorInvalidOperation("%s: PIXEL_PACK_BUFFER must be null.", funcName);
return;
}
////
@@ -1340,20 +1342,21 @@ WebGLContext::ReadPixels(GLint x, GLint
////
ReadPixelsImpl(x, y, width, height, format, type, bytes, byteLen);
}
void
WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,
- GLenum type, WebGLsizeiptr offset, ErrorResult& out_error)
+ GLenum type, WebGLsizeiptr offset,
+ CallerType aCallerType, ErrorResult& out_error)
{
const char funcName[] = "readPixels";
- if (!ReadPixels_SharedPrecheck(&out_error))
+ if (!ReadPixels_SharedPrecheck(aCallerType, out_error))
return;
const auto& buffer = ValidateBufferSelection(funcName, LOCAL_GL_PIXEL_PACK_BUFFER);
if (!buffer)
return;
//////
--- a/dom/canvas/test/mochitest.ini
+++ b/dom/canvas/test/mochitest.ini
@@ -246,16 +246,17 @@ tags = imagebitmap
tags = imagebitmap
[test_imagebitmap_transfer.html]
tags = imagebitmap
[test_ImageData_ctor.html]
[test_isPointInStroke.html]
[test_mozGetAsFile.html]
[test_strokeText_throw.html]
[test_toBlob.html]
+[test_toBlob_zero_dimension.html]
[test_toDataURL_alpha.html]
[test_toDataURL_lowercase_ascii.html]
[test_toDataURL_parameters.html]
[test_windingRuleUndefined.html]
[test_2d.fillText.gradient.html]
[test_2d_composite_canvaspattern_setTransform.html]
[test_createPattern_broken.html]
[test_filter.html]
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/test_toBlob_zero_dimension.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<title>Canvas test: toBlob</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+<canvas id="c" width="100" height="0"></canvas>
+<script>
+
+function testblob(blob) {
+ is(blob, null, "Null blob expected");
+}
+
+function test1(canvas)
+{
+ canvas.toBlob(function(blob) {
+ testblob(blob);
+ test2(canvas);
+ }, "image/png");
+}
+
+function test2(canvas)
+{
+ canvas.width = 0;
+ canvas.height = 100;
+ canvas.toBlob(function(blob) {
+ testblob(blob);
+ SimpleTest.finish();
+ }, "image/png");
+}
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function () {
+
+var canvas = document.getElementById('c');
+var ctx = canvas.getContext('2d');
+test1(canvas);
+
+});
+</script>
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -32,20 +32,16 @@
#include "nsJSEnvironment.h"
#include "nsLayoutUtils.h"
#include "nsPIWindowRoot.h"
#include "WorkerPrivate.h"
namespace mozilla {
namespace dom {
-namespace workers {
-extern bool IsCurrentThreadRunningChromeWorker();
-} // namespace workers
-
static char *sPopupAllowedEvents;
static bool sReturnHighResTimeStamp = false;
static bool sReturnHighResTimeStampIsSet = false;
Event::Event(EventTarget* aOwner,
nsPresContext* aPresContext,
WidgetEvent* aEvent)
@@ -362,17 +358,17 @@ Event::SetTrusted(bool aTrusted)
{
mEvent->mFlags.mIsTrusted = aTrusted;
}
bool
Event::Init(mozilla::dom::EventTarget* aGlobal)
{
if (!mIsMainThreadEvent) {
- return nsContentUtils::ThreadsafeIsCallerChrome();
+ return workers::IsCurrentThreadRunningChromeWorker();
}
bool trusted = false;
nsCOMPtr<nsPIDOMWindowInner> w = do_QueryInterface(aGlobal);
if (w) {
nsCOMPtr<nsIDocument> d = w->GetExtantDoc();
if (d) {
trusted = nsContentUtils::IsChromeDoc(d);
nsIPresShell* s = d->GetShell();
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -15,16 +15,17 @@
#include "nsCharSeparatedTokenizer.h"
#include "nsDOMString.h"
#include "nsNetUtil.h"
#include "nsReadableUtils.h"
#include "nsStreamUtils.h"
#include "nsStringStream.h"
#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/BodyUtil.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/FetchDriver.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FormData.h"
#include "mozilla/dom/Headers.h"
#include "mozilla/dom/MutableBlobStreamListener.h"
#include "mozilla/dom/Promise.h"
@@ -171,30 +172,28 @@ public:
// ...but release it before calling Fetch, because mResolver's callback can
// be called synchronously and they want the mutex, too.
return fetch->Fetch(mResolver);
}
};
already_AddRefed<Promise>
FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
- const RequestInit& aInit, ErrorResult& aRv)
+ const RequestInit& aInit, CallerType aCallerType, ErrorResult& aRv)
{
RefPtr<Promise> p = Promise::Create(aGlobal, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Double check that we have chrome privileges if the Request's content
- // policy type has been overridden. Note, we must do this before
- // entering the global below. Otherwise the IsCallerChrome() will
- // always fail.
+ // policy type has been overridden.
MOZ_ASSERT_IF(aInput.IsRequest() &&
aInput.GetAsRequest().IsContentPolicyTypeOverridden(),
- nsContentUtils::IsCallerChrome());
+ aCallerType == CallerType::System);
AutoJSAPI jsapi;
if (!jsapi.Init(aGlobal)) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return nullptr;
}
JSContext* cx = jsapi.cx();
--- a/dom/fetch/Fetch.h
+++ b/dom/fetch/Fetch.h
@@ -27,24 +27,26 @@ class nsIGlobalObject;
namespace mozilla {
namespace dom {
class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
class BlobImpl;
class InternalRequest;
class OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
class RequestOrUSVString;
+enum class CallerType : uint32_t;
namespace workers {
class WorkerPrivate;
} // namespace workers
already_AddRefed<Promise>
FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
- const RequestInit& aInit, ErrorResult& aRv);
+ const RequestInit& aInit, CallerType aCallerType,
+ ErrorResult& aRv);
nsresult
UpdateRequestReferrer(nsIGlobalObject* aGlobal, InternalRequest* aRequest);
/*
* Creates an nsIInputStream based on the fetch specifications 'extract a byte
* stream algorithm' - http://fetch.spec.whatwg.org/#concept-bodyinit-extract.
* Stores content type in out param aContentType.
--- a/dom/file/File.cpp
+++ b/dom/file/File.cpp
@@ -494,19 +494,20 @@ File::GetLastModifiedDate(ErrorResult& a
int64_t
File::GetLastModified(ErrorResult& aRv)
{
return mImpl->GetLastModified(aRv);
}
void
-File::GetMozFullPath(nsAString& aFilename, ErrorResult& aRv) const
+File::GetMozFullPath(nsAString& aFilename, SystemCallerGuarantee aGuarantee,
+ ErrorResult& aRv) const
{
- mImpl->GetMozFullPath(aFilename, aRv);
+ mImpl->GetMozFullPath(aFilename, aGuarantee, aRv);
}
void
File::GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const
{
mImpl->GetMozFullPathInternal(aFileName, aRv);
}
@@ -575,28 +576,25 @@ File::Constructor(const GlobalObject& aG
RefPtr<File> file = new File(aGlobal.GetAsSupports(), impl);
return file.forget();
}
/* static */ already_AddRefed<File>
File::CreateFromNsIFile(const GlobalObject& aGlobal,
nsIFile* aData,
const ChromeFilePropertyBag& aBag,
+ SystemCallerGuarantee aGuarantee,
ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread());
- if (!nsContentUtils::IsCallerChrome()) {
- aRv.Throw(NS_ERROR_FAILURE);
- return nullptr;
- }
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
RefPtr<MultipartBlobImpl> impl = new MultipartBlobImpl(EmptyString());
- impl->InitializeChromeFile(window, aData, aBag, true, aRv);
+ impl->InitializeChromeFile(window, aData, aBag, true, aGuarantee, aRv);
if (aRv.Failed()) {
return nullptr;
}
MOZ_ASSERT(impl->IsFile());
if (aBag.mLastModified.WasPassed()) {
impl->SetLastModified(aBag.mLastModified.Value());
}
@@ -604,27 +602,23 @@ File::CreateFromNsIFile(const GlobalObje
RefPtr<File> domFile = new File(aGlobal.GetAsSupports(), impl);
return domFile.forget();
}
/* static */ already_AddRefed<File>
File::CreateFromFileName(const GlobalObject& aGlobal,
const nsAString& aData,
const ChromeFilePropertyBag& aBag,
+ SystemCallerGuarantee aGuarantee,
ErrorResult& aRv)
{
- if (!nsContentUtils::ThreadsafeIsCallerChrome()) {
- aRv.ThrowTypeError<MSG_MISSING_ARGUMENTS>(NS_LITERAL_STRING("File"));
- return nullptr;
- }
-
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
RefPtr<MultipartBlobImpl> impl = new MultipartBlobImpl(EmptyString());
- impl->InitializeChromeFile(window, aData, aBag, aRv);
+ impl->InitializeChromeFile(window, aData, aBag, aGuarantee, aRv);
if (aRv.Failed()) {
return nullptr;
}
MOZ_ASSERT(impl->IsFile());
if (aBag.mLastModified.WasPassed()) {
impl->SetLastModified(aBag.mLastModified.Value());
}
@@ -685,36 +679,23 @@ BlobImplBase::GetPath(nsAString& aPath)
void
BlobImplBase::SetPath(const nsAString& aPath)
{
MOZ_ASSERT(mIsFile, "Should only be called on files");
mPath = aPath;
}
void
-BlobImplBase::GetMozFullPath(nsAString& aFileName, ErrorResult& aRv) const
+BlobImplBase::GetMozFullPath(nsAString& aFileName,
+ SystemCallerGuarantee /* unused */,
+ ErrorResult& aRv) const
{
MOZ_ASSERT(mIsFile, "Should only be called on files");
- aFileName.Truncate();
-
- if (NS_IsMainThread()) {
- if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
- GetMozFullPathInternal(aFileName, aRv);
- }
-
- return;
- }
-
- WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
- MOZ_ASSERT(workerPrivate);
-
- if (workerPrivate->UsesSystemPrincipal()) {
- GetMozFullPathInternal(aFileName, aRv);
- }
+ GetMozFullPathInternal(aFileName, aRv);
}
void
BlobImplBase::GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const
{
if (!mIsFile) {
aRv.Throw(NS_ERROR_FAILURE);
return;
--- a/dom/file/File.h
+++ b/dom/file/File.h
@@ -203,40 +203,43 @@ public:
const FilePropertyBag& aBag,
ErrorResult& aRv);
// ChromeOnly
static already_AddRefed<File>
CreateFromFileName(const GlobalObject& aGlobal,
const nsAString& aData,
const ChromeFilePropertyBag& aBag,
+ SystemCallerGuarantee aGuarantee,
ErrorResult& aRv);
// ChromeOnly
static already_AddRefed<File>
CreateFromNsIFile(const GlobalObject& aGlobal,
nsIFile* aData,
const ChromeFilePropertyBag& aBag,
+ SystemCallerGuarantee aGuarantee,
ErrorResult& aRv);
void GetName(nsAString& aName) const;
int64_t GetLastModified(ErrorResult& aRv);
Date GetLastModifiedDate(ErrorResult& aRv);
// GetPath and SetPath are currently used only for the webkitRelativePath
// attribute and they are only used when this File object is created from a
// Directory, generated by a Directory Picker.
void GetPath(nsAString& aName) const;
void SetPath(const nsAString& aName);
- void GetMozFullPath(nsAString& aFilename, ErrorResult& aRv) const;
+ void GetMozFullPath(nsAString& aFilename, SystemCallerGuarantee aGuarantee,
+ ErrorResult& aRv) const;
void GetMozFullPathInternal(nsAString& aName, ErrorResult& aRv) const;
protected:
virtual bool HasFileInterface() const override { return true; }
private:
// File constructor should never be used directly. Use Blob::Create or
@@ -260,17 +263,19 @@ public:
virtual void GetPath(nsAString& aName) const = 0;
virtual void SetPath(const nsAString& aName) = 0;
virtual int64_t GetLastModified(ErrorResult& aRv) = 0;
virtual void SetLastModified(int64_t aLastModified) = 0;
- virtual void GetMozFullPath(nsAString& aName, ErrorResult& aRv) const = 0;
+ virtual void GetMozFullPath(nsAString& aName,
+ SystemCallerGuarantee /* unused */,
+ ErrorResult& aRv) const = 0;
virtual void GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const = 0;
virtual uint64_t GetSize(ErrorResult& aRv) = 0;
virtual void GetType(nsAString& aType) = 0;
/**
@@ -404,17 +409,19 @@ public:
virtual void GetPath(nsAString& aName) const override;
virtual void SetPath(const nsAString& aName) override;
virtual int64_t GetLastModified(ErrorResult& aRv) override;
virtual void SetLastModified(int64_t aLastModified) override;
- virtual void GetMozFullPath(nsAString& aName, ErrorResult& aRv) const override;
+ virtual void GetMozFullPath(nsAString& aName,
+ SystemCallerGuarantee /* unused */,
+ ErrorResult& aRv) const override;
virtual void GetMozFullPathInternal(nsAString& aFileName,
ErrorResult& aRv) const override;
virtual uint64_t GetSize(ErrorResult& aRv) override
{
return mLength;
}
--- a/dom/file/MultipartBlobImpl.cpp
+++ b/dom/file/MultipartBlobImpl.cpp
@@ -323,26 +323,25 @@ MultipartBlobImpl::SetMutable(bool aMuta
return NS_OK;
}
void
MultipartBlobImpl::InitializeChromeFile(nsPIDOMWindowInner* aWindow,
nsIFile* aFile,
const ChromeFilePropertyBag& aBag,
bool aIsFromNsIFile,
+ SystemCallerGuarantee /* unused */,
ErrorResult& aRv)
{
MOZ_ASSERT(!mImmutable, "Something went wrong ...");
if (mImmutable) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
- MOZ_ASSERT(nsContentUtils::IsCallerChrome());
-
mName = aBag.mName;
mContentType = aBag.mType;
mIsFromNsIFile = aIsFromNsIFile;
bool exists;
aRv = aFile->Exists(&exists);
if (NS_WARN_IF(aRv.Failed())) {
return;
@@ -394,25 +393,26 @@ MultipartBlobImpl::InitializeChromeFile(
SetLengthAndModifiedDate(aRv);
NS_WARNING_ASSERTION(!aRv.Failed(), "SetLengthAndModifiedDate failed");
}
void
MultipartBlobImpl::InitializeChromeFile(nsPIDOMWindowInner* aWindow,
const nsAString& aData,
const ChromeFilePropertyBag& aBag,
+ SystemCallerGuarantee aGuarantee,
ErrorResult& aRv)
{
nsCOMPtr<nsIFile> file;
aRv = NS_NewLocalFile(aData, false, getter_AddRefs(file));
if (NS_WARN_IF(aRv.Failed())) {
return;
}
- InitializeChromeFile(aWindow, file, aBag, false, aRv);
+ InitializeChromeFile(aWindow, file, aBag, false, aGuarantee, aRv);
}
bool
MultipartBlobImpl::MayBeClonedToOtherThreads() const
{
for (uint32_t i = 0; i < mBlobImpls.Length(); ++i) {
if (!mBlobImpls[i]->MayBeClonedToOtherThreads()) {
return false;
--- a/dom/file/MultipartBlobImpl.h
+++ b/dom/file/MultipartBlobImpl.h
@@ -7,16 +7,17 @@
#ifndef mozilla_dom_MultipartBlobImpl_h
#define mozilla_dom_MultipartBlobImpl_h
#include "mozilla/Attributes.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Move.h"
#include "mozilla/dom/File.h"
+#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/BlobBinding.h"
#include "mozilla/dom/FileBinding.h"
#include <algorithm>
#include "nsPIDOMWindow.h"
namespace mozilla {
namespace dom {
@@ -58,22 +59,24 @@ public:
const Sequence<Blob::BlobPart>& aData,
const nsAString& aContentType,
bool aNativeEOL,
ErrorResult& aRv);
void InitializeChromeFile(nsPIDOMWindowInner* aWindow,
const nsAString& aData,
const ChromeFilePropertyBag& aBag,
+ SystemCallerGuarantee aGuarantee,
ErrorResult& aRv);
void InitializeChromeFile(nsPIDOMWindowInner* aWindow,
nsIFile* aData,
const ChromeFilePropertyBag& aBag,
bool aIsFromNsIFile,
+ SystemCallerGuarantee /* unused */,
ErrorResult& aRv);
virtual already_AddRefed<BlobImpl>
CreateSlice(uint64_t aStart, uint64_t aLength,
const nsAString& aContentType,
ErrorResult& aRv) override;
virtual uint64_t GetSize(ErrorResult& aRv) override
--- a/dom/file/ipc/Blob.cpp
+++ b/dom/file/ipc/Blob.cpp
@@ -2078,17 +2078,18 @@ public:
int64_t
GetLastModified(ErrorResult& aRv) override;
void
SetLastModified(int64_t aLastModified) override;
void
- GetMozFullPath(nsAString& aName, ErrorResult& aRv) const override;
+ GetMozFullPath(nsAString& aName, SystemCallerGuarantee aGuarantee,
+ ErrorResult& aRv) const override;
void
GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const override;
bool
IsDirectory() const override;
uint64_t
@@ -2865,19 +2866,21 @@ void
BlobParent::
RemoteBlobImpl::SetLastModified(int64_t aLastModified)
{
MOZ_CRASH("SetLastModified of a remote blob is not allowed!");
}
void
BlobParent::
-RemoteBlobImpl::GetMozFullPath(nsAString& aName, ErrorResult& aRv) const
+RemoteBlobImpl::GetMozFullPath(nsAString& aName,
+ SystemCallerGuarantee aGuarantee,
+ ErrorResult& aRv) const
{
- mBlobImpl->GetMozFullPath(aName, aRv);
+ mBlobImpl->GetMozFullPath(aName, aGuarantee, aRv);
}
void
BlobParent::
RemoteBlobImpl::GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const
{
mBlobImpl->GetMozFullPathInternal(aFileName, aRv);
}
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -2800,17 +2800,22 @@ nsHTMLDocument::EditingStateChanged()
// Set the editor to not insert br's on return when in p
// elements by default.
// XXX Do we only want to do this for designMode?
// Note that it doesn't matter what CallerType we pass, because the callee
// doesn't use it for this command. Play it safe and pass the more
// restricted one.
ErrorResult errorResult;
Unused << ExecCommand(NS_LITERAL_STRING("insertBrOnReturn"), false,
- NS_LITERAL_STRING("false"), CallerType::NonSystem,
+ NS_LITERAL_STRING("false"),
+ // Principal doesn't matter here, because the
+ // insertBrOnReturn command doesn't use it. Still
+ // it's too bad we can't easily grab a nullprincipal
+ // from somewhere without allocating one..
+ *NodePrincipal(),
errorResult);
if (errorResult.Failed()) {
// Editor setup failed. Editing is not on after all.
// XXX Should we reset the editable flag on nodes?
editSession->TearDownEditorOnWindow(window);
mEditingState = eOff;
@@ -3129,17 +3134,17 @@ ConvertToMidasInternalCommand(const nsAS
outCommandID, dummyCString,
dummyBool, dummyBool, true);
}
bool
nsHTMLDocument::ExecCommand(const nsAString& commandID,
bool doShowUI,
const nsAString& value,
- CallerType aCallerType,
+ nsIPrincipal& aSubjectPrincipal,
ErrorResult& rv)
{
// for optional parameters see dom/src/base/nsHistory.cpp: HistoryImpl::Go()
// this might add some ugly JS dependencies?
nsAutoCString cmdToDispatch, paramStr;
bool isBool, boolVal;
if (!ConvertToMidasInternalCommand(commandID, value,
@@ -3159,17 +3164,17 @@ nsHTMLDocument::ExecCommand(const nsAStr
// if they are requesting UI from us, let's fail since we have no UI
if (doShowUI) {
return false;
}
// special case for cut & copy
// cut & copy are allowed in non editable documents
if (isCutCopy) {
- if (!nsContentUtils::IsCutCopyAllowed()) {
+ if (!nsContentUtils::IsCutCopyAllowed(&aSubjectPrincipal)) {
// We have rejected the event due to it not being performed in an
// input-driven context therefore, we report the error to the console.
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("DOM"), this,
nsContentUtils::eDOM_PROPERTIES,
"ExecCommandCutCopyDeniedNotInputDriven");
return false;
}
@@ -3189,17 +3194,17 @@ nsHTMLDocument::ExecCommand(const nsAStr
}
if (commandID.LowerCaseEqualsLiteral("gethtml")) {
rv.Throw(NS_ERROR_FAILURE);
return false;
}
bool restricted = commandID.LowerCaseEqualsLiteral("paste");
- if (restricted && aCallerType != CallerType::System) {
+ if (restricted && !nsContentUtils::IsSystemPrincipal(&aSubjectPrincipal)) {
return false;
}
// get command manager and dispatch command to our window if it's acceptable
nsCOMPtr<nsICommandManager> cmdMgr;
GetMidasCommandManager(getter_AddRefs(cmdMgr));
if (!cmdMgr) {
rv.Throw(NS_ERROR_FAILURE);
@@ -3255,34 +3260,34 @@ nsHTMLDocument::ExecCommand(const nsAStr
rv = cmdMgr->DoCommand(cmdToDispatch.get(), cmdParams, window);
}
return !rv.Failed();
}
bool
nsHTMLDocument::QueryCommandEnabled(const nsAString& commandID,
- CallerType aCallerType,
+ nsIPrincipal& aSubjectPrincipal,
ErrorResult& rv)
{
nsAutoCString cmdToDispatch;
if (!ConvertToMidasInternalCommand(commandID, cmdToDispatch)) {
return false;
}
// cut & copy are always allowed
bool isCutCopy = commandID.LowerCaseEqualsLiteral("cut") ||
commandID.LowerCaseEqualsLiteral("copy");
if (isCutCopy) {
- return nsContentUtils::IsCutCopyAllowed();
+ return nsContentUtils::IsCutCopyAllowed(&aSubjectPrincipal);
}
// Report false for restricted commands
bool restricted = commandID.LowerCaseEqualsLiteral("paste");
- if (restricted && aCallerType != CallerType::System) {
+ if (restricted && !nsContentUtils::IsSystemPrincipal(&aSubjectPrincipal)) {
return false;
}
// if editing is not on, bail
if (!IsEditingOnAfterFlush()) {
return false;
}
@@ -3455,16 +3460,19 @@ nsHTMLDocument::QueryCommandSupported(co
// may also be disallowed to be called from non-privileged content.
// For that reason, we report the support status of corresponding
// command accordingly.
if (aCallerType != CallerType::System) {
if (commandID.LowerCaseEqualsLiteral("paste")) {
return false;
}
if (nsContentUtils::IsCutCopyRestricted()) {
+ // XXXbz should we worry about correctly reporting "true" in the
+ // "restricted, but we're an addon with clipboardWrite permissions" case?
+ // See also nsContentUtils::IsCutCopyAllowed.
if (commandID.LowerCaseEqualsLiteral("cut") ||
commandID.LowerCaseEqualsLiteral("copy")) {
return false;
}
}
}
// commandID is supported if it can be converted to a Midas command
--- a/dom/html/nsHTMLDocument.h
+++ b/dom/html/nsHTMLDocument.h
@@ -213,20 +213,20 @@ public:
void SetDesignMode(const nsAString& aDesignMode,
nsIPrincipal& aSubjectPrincipal,
mozilla::ErrorResult& rv);
void SetDesignMode(const nsAString& aDesignMode,
const mozilla::Maybe<nsIPrincipal*>& aSubjectPrincipal,
mozilla::ErrorResult& rv);
bool ExecCommand(const nsAString& aCommandID, bool aDoShowUI,
const nsAString& aValue,
- mozilla::dom::CallerType aCallerType,
+ nsIPrincipal& aSubjectPrincipal,
mozilla::ErrorResult& rv);
bool QueryCommandEnabled(const nsAString& aCommandID,
- mozilla::dom::CallerType aCallerType,
+ nsIPrincipal& aSubjectPrincipal,
mozilla::ErrorResult& rv);
bool QueryCommandIndeterm(const nsAString& aCommandID,
mozilla::ErrorResult& rv);
bool QueryCommandState(const nsAString& aCommandID, mozilla::ErrorResult& rv);
bool QueryCommandSupported(const nsAString& aCommandID,
mozilla::dom::CallerType aCallerType);
void QueryCommandValue(const nsAString& aCommandID, nsAString& aValue,
mozilla::ErrorResult& rv);
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -436,17 +436,17 @@ support-files =
[test_formData.html]
[test_formSubmission.html]
skip-if = toolkit == 'android' #TIMED_OUT
[test_formSubmission2.html]
skip-if = toolkit == 'android'
[test_formelements.html]
[test_fullscreen-api.html]
tags = fullscreen
-skip-if = toolkit == 'android'
+skip-if = toolkit == 'android' || (e10s && os == 'linux') # Bug 1307347
support-files =
file_fullscreen-api.html
file_fullscreen-backdrop.html
file_fullscreen-denied-inner.html
file_fullscreen-denied.html
file_fullscreen-esc-exit-inner.html
file_fullscreen-esc-exit.html
file_fullscreen-hidden.html
--- a/dom/indexedDB/FileSnapshot.h
+++ b/dom/indexedDB/FileSnapshot.h
@@ -79,19 +79,20 @@ private:
virtual void
SetLastModified(int64_t aLastModified) override
{
mBlobImpl->SetLastModified(aLastModified);
}
virtual void
- GetMozFullPath(nsAString& aName, ErrorResult& aRv) const override
+ GetMozFullPath(nsAString& aName, SystemCallerGuarantee aGuarantee,
+ ErrorResult& aRv) const override
{
- mBlobImpl->GetMozFullPath(aName, aRv);
+ mBlobImpl->GetMozFullPath(aName, aGuarantee, aRv);
}
virtual void
GetMozFullPathInternal(nsAString& aFileName, ErrorResult& aRv) const override
{
mBlobImpl->GetMozFullPathInternal(aFileName, aRv);
}
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -449,54 +449,60 @@ IDBFactory::IncrementParentLoggingReques
mBackgroundActor->SendIncrementLoggingRequestSerialNumber();
}
already_AddRefed<IDBOpenDBRequest>
IDBFactory::Open(JSContext* aCx,
const nsAString& aName,
uint64_t aVersion,
+ CallerType aCallerType,
ErrorResult& aRv)
{
return OpenInternal(aCx,
/* aPrincipal */ nullptr,
aName,
Optional<uint64_t>(aVersion),
Optional<StorageType>(),
/* aDeleting */ false,
+ aCallerType,
aRv);
}
already_AddRefed<IDBOpenDBRequest>
IDBFactory::Open(JSContext* aCx,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
+ CallerType aCallerType,
ErrorResult& aRv)
{
return OpenInternal(aCx,
/* aPrincipal */ nullptr,
aName,
aOptions.mVersion,
aOptions.mStorage,
/* aDeleting */ false,
+ aCallerType,
aRv);
}
already_AddRefed<IDBOpenDBRequest>
IDBFactory::DeleteDatabase(JSContext* aCx,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
+ CallerType aCallerType,
ErrorResult& aRv)
{
return OpenInternal(aCx,
/* aPrincipal */ nullptr,
aName,
Optional<uint64_t>(),
aOptions.mStorage,
/* aDeleting */ true,
+ aCallerType,
aRv);
}
int16_t
IDBFactory::Cmp(JSContext* aCx, JS::Handle<JS::Value> aFirst,
JS::Handle<JS::Value> aSecond, ErrorResult& aRv)
{
Key first, second;
@@ -520,99 +526,107 @@ IDBFactory::Cmp(JSContext* aCx, JS::Hand
return Key::CompareKeys(first, second);
}
already_AddRefed<IDBOpenDBRequest>
IDBFactory::OpenForPrincipal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
uint64_t aVersion,
+ SystemCallerGuarantee aGuarantee,
ErrorResult& aRv)
{
MOZ_ASSERT(aPrincipal);
if (!NS_IsMainThread()) {
- MOZ_CRASH("Figure out security checks for workers!");
+ MOZ_CRASH("Figure out security checks for workers! What's this aPrincipal "
+ "we have on a worker thread?");
}
- MOZ_ASSERT(nsContentUtils::IsCallerChrome());
return OpenInternal(aCx,
aPrincipal,
aName,
Optional<uint64_t>(aVersion),
Optional<StorageType>(),
/* aDeleting */ false,
+ aGuarantee,
aRv);
}
already_AddRefed<IDBOpenDBRequest>
IDBFactory::OpenForPrincipal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
+ SystemCallerGuarantee aGuarantee,
ErrorResult& aRv)
{
MOZ_ASSERT(aPrincipal);
if (!NS_IsMainThread()) {
- MOZ_CRASH("Figure out security checks for workers!");
+ MOZ_CRASH("Figure out security checks for workers! What's this aPrincipal "
+ "we have on a worker thread?");
}
- MOZ_ASSERT(nsContentUtils::IsCallerChrome());
return OpenInternal(aCx,
aPrincipal,
aName,
aOptions.mVersion,
aOptions.mStorage,
/* aDeleting */ false,
+ aGuarantee,
aRv);
}
already_AddRefed<IDBOpenDBRequest>
IDBFactory::DeleteForPrincipal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
+ SystemCallerGuarantee aGuarantee,
ErrorResult& aRv)
{
MOZ_ASSERT(aPrincipal);
if (!NS_IsMainThread()) {
- MOZ_CRASH("Figure out security checks for workers!");
+ MOZ_CRASH("Figure out security checks for workers! What's this aPrincipal "
+ "we have on a worker thread?");
}
- MOZ_ASSERT(nsContentUtils::IsCallerChrome());
return OpenInternal(aCx,
aPrincipal,
aName,
Optional<uint64_t>(),
aOptions.mStorage,
/* aDeleting */ true,
+ aGuarantee,
aRv);
}
already_AddRefed<IDBOpenDBRequest>
IDBFactory::OpenInternal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
const Optional<uint64_t>& aVersion,
const Optional<StorageType>& aStorageType,
bool aDeleting,
+ CallerType aCallerType,
ErrorResult& aRv)
{
MOZ_ASSERT(mWindow || mOwningObject);
MOZ_ASSERT_IF(!mWindow, !mPrivateBrowsingMode);
CommonFactoryRequestParams commonParams;
commonParams.privateBrowsingMode() = mPrivateBrowsingMode;
PrincipalInfo& principalInfo = commonParams.principalInfo();
if (aPrincipal) {
if (!NS_IsMainThread()) {
- MOZ_CRASH("Figure out security checks for workers!");
+ MOZ_CRASH("Figure out security checks for workers! What's this "
+ "aPrincipal we have on a worker thread?");
}
- MOZ_ASSERT(nsContentUtils::IsCallerChrome());
+ MOZ_ASSERT(aCallerType == CallerType::System);
MOZ_DIAGNOSTIC_ASSERT(mPrivateBrowsingMode == (aPrincipal->GetPrivateBrowsingId() > 0));
if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(aPrincipal,
&principalInfo)))) {
IDB_REPORT_INTERNAL_ERR();
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
return nullptr;
}
--- a/dom/indexedDB/IDBFactory.h
+++ b/dom/indexedDB/IDBFactory.h
@@ -3,16 +3,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_idbfactory_h__
#define mozilla_dom_idbfactory_h__
#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/StorageTypeBinding.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsISupports.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsWrapperCache.h"
@@ -33,16 +34,17 @@ class PrincipalInfo;
} // namespace ipc
namespace dom {
struct IDBOpenDBOptions;
class IDBOpenDBRequest;
template <typename> class Optional;
class TabChild;
+enum class CallerType : uint32_t;
namespace indexedDB {
class BackgroundFactoryChild;
class FactoryRequestParams;
class LoggingInfo;
}
class IDBFactory final
@@ -157,55 +159,61 @@ public:
bool
IsChrome() const;
already_AddRefed<IDBOpenDBRequest>
Open(JSContext* aCx,
const nsAString& aName,
uint64_t aVersion,
+ CallerType aCallerType,
ErrorResult& aRv);
already_AddRefed<IDBOpenDBRequest>
Open(JSContext* aCx,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
+ CallerType aCallerType,
ErrorResult& aRv);
already_AddRefed<IDBOpenDBRequest>
DeleteDatabase(JSContext* aCx,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
+ CallerType aCallerType,
ErrorResult& aRv);
int16_t
Cmp(JSContext* aCx,
JS::Handle<JS::Value> aFirst,
JS::Handle<JS::Value> aSecond,
ErrorResult& aRv);
already_AddRefed<IDBOpenDBRequest>
OpenForPrincipal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
uint64_t aVersion,
+ SystemCallerGuarantee,
ErrorResult& aRv);
already_AddRefed<IDBOpenDBRequest>
OpenForPrincipal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
+ SystemCallerGuarantee,
ErrorResult& aRv);
already_AddRefed<IDBOpenDBRequest>
DeleteForPrincipal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
const IDBOpenDBOptions& aOptions,
+ SystemCallerGuarantee,
ErrorResult& aRv);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(IDBFactory)
// nsWrapperCache
virtual JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
@@ -233,16 +241,17 @@ private:
already_AddRefed<IDBOpenDBRequest>
OpenInternal(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsAString& aName,
const Optional<uint64_t>& aVersion,
const Optional<StorageType>& aStorageType,
bool aDeleting,
+ CallerType aCallerType,
ErrorResult& aRv);
nsresult
BackgroundActorCreated(PBackgroundChild* aBackgroundActor,
const indexedDB::LoggingInfo& aLoggingInfo);
void
BackgroundActorFailed();
--- a/dom/media/MediaDevices.cpp
+++ b/dom/media/MediaDevices.cpp
@@ -169,28 +169,30 @@ MediaDevices::~MediaDevices()
}
NS_IMPL_ISUPPORTS(MediaDevices::GumResolver, nsIDOMGetUserMediaSuccessCallback)
NS_IMPL_ISUPPORTS(MediaDevices::EnumDevResolver, nsIGetUserMediaDevicesSuccessCallback)
NS_IMPL_ISUPPORTS(MediaDevices::GumRejecter, nsIDOMGetUserMediaErrorCallback)
already_AddRefed<Promise>
MediaDevices::GetUserMedia(const MediaStreamConstraints& aConstraints,
+ CallerType aCallerType,
ErrorResult &aRv)
{
nsPIDOMWindowInner* window = GetOwner();
nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
RefPtr<Promise> p = Promise::Create(go, aRv);
NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
RefPtr<GumResolver> resolver = new GumResolver(p);
RefPtr<GumRejecter> rejecter = new GumRejecter(p);
aRv = MediaManager::Get()->GetUserMedia(window, aConstraints,
- resolver, rejecter);
+ resolver, rejecter,
+ aCallerType);
return p.forget();
}
already_AddRefed<Promise>
MediaDevices::EnumerateDevices(ErrorResult &aRv)
{
nsPIDOMWindowInner* window = GetOwner();
nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(window);
--- a/dom/media/MediaDevices.h
+++ b/dom/media/MediaDevices.h
@@ -34,17 +34,18 @@ public:
NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOM_MEDIADEVICES_IMPLEMENTATION_IID)
JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override;
// No code needed, as MediaTrackSupportedConstraints members default to true.
void GetSupportedConstraints(MediaTrackSupportedConstraints& aResult) {};
already_AddRefed<Promise>
- GetUserMedia(const MediaStreamConstraints& aConstraints, ErrorResult &aRv);
+ GetUserMedia(const MediaStreamConstraints& aConstraints,
+ CallerType aCallerType, ErrorResult &aRv);
already_AddRefed<Promise>
EnumerateDevices(ErrorResult &aRv);
virtual void OnDeviceChange() override;
mozilla::dom::EventHandlerNonNull* GetOndevicechange();
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -33,16 +33,17 @@
#include "nsICryptoHMAC.h"
#include "nsIKeyModule.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsIInputStream.h"
#include "nsILineInputStream.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Types.h"
#include "mozilla/PeerIdentity.h"
+#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/MediaStreamBinding.h"
#include "mozilla/dom/MediaStreamTrackBinding.h"
#include "mozilla/dom/GetUserMediaRequestBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/MediaDevices.h"
#include "mozilla/Base64.h"
@@ -234,17 +235,18 @@ public:
void NotifyChromeOfTrackStops();
typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
already_AddRefed<PledgeVoid>
ApplyConstraintsToTrack(nsPIDOMWindowInner* aWindow,
TrackID aID,
- const dom::MediaTrackConstraints& aConstraints);
+ const dom::MediaTrackConstraints& aConstraints,
+ dom::CallerType aCallerType);
// mVideo/AudioDevice are set by Activate(), so we assume they're capturing
// if set and represent a real capture device.
bool CapturingVideo()
{
MOZ_ASSERT(NS_IsMainThread());
return mVideoDevice && !mStopped &&
!mVideoDevice->GetSource()->IsAvailable() &&
@@ -1107,26 +1109,28 @@ public:
const PeerIdentity* GetPeerIdentity() const override
{
return mPeerIdentity;
}
already_AddRefed<PledgeVoid>
ApplyConstraints(nsPIDOMWindowInner* aWindow,
- const MediaTrackConstraints& aConstraints) override
+ const MediaTrackConstraints& aConstraints,
+ dom::CallerType aCallerType) override
{
if (sInShutdown || !mListener) {
// Track has been stopped, or we are in shutdown. In either case
// there's no observable outcome, so pretend we succeeded.
RefPtr<PledgeVoid> p = new PledgeVoid();
p->Resolve(false);
return p.forget();
}
- return mListener->ApplyConstraintsToTrack(aWindow, mTrackID, aConstraints);
+ return mListener->ApplyConstraintsToTrack(aWindow, mTrackID,
+ aConstraints, aCallerType);
}
void
GetSettings(dom::MediaTrackSettings& aOutSettings) override
{
if (mListener) {
mListener->GetSettings(aOutSettings, mTrackID);
}
@@ -1993,17 +1997,18 @@ enum class GetUserMediaSecurityState {
* The entry point for this file. A call from Navigator::mozGetUserMedia
* will end up here. MediaManager is a singleton that is responsible
* for handling all incoming getUserMedia calls from every window.
*/
nsresult
MediaManager::GetUserMedia(nsPIDOMWindowInner* aWindow,
const MediaStreamConstraints& aConstraintsPassedIn,
nsIDOMGetUserMediaSuccessCallback* aOnSuccess,
- nsIDOMGetUserMediaErrorCallback* aOnFailure)
+ nsIDOMGetUserMediaErrorCallback* aOnFailure,
+ dom::CallerType aCallerType)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aWindow);
MOZ_ASSERT(aOnFailure);
MOZ_ASSERT(aOnSuccess);
nsCOMPtr<nsIDOMGetUserMediaSuccessCallback> onSuccess(aOnSuccess);
nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure(aOnFailure);
uint64_t windowID = aWindow->WindowID();
@@ -2031,17 +2036,17 @@ MediaManager::GetUserMedia(nsPIDOMWindow
}
// Determine permissions early (while we still have a stack).
nsIURI* docURI = aWindow->GetDocumentURI();
if (!docURI) {
return NS_ERROR_UNEXPECTED;
}
- bool isChrome = nsContentUtils::IsCallerChrome();
+ bool isChrome = (aCallerType == dom::CallerType::System);
bool privileged = isChrome ||
Preferences::GetBool("media.navigator.permission.disabled", false);
bool isHTTPS = false;
docURI->SchemeIs("https", &isHTTPS);
nsCString host;
nsresult rv = docURI->GetHost(host);
// Test for some other schemes that ServiceWorker recognizes
bool isFile;
@@ -3461,17 +3466,18 @@ GetUserMediaCallbackMediaStreamListener:
}
// ApplyConstraints for track
auto
GetUserMediaCallbackMediaStreamListener::ApplyConstraintsToTrack(
nsPIDOMWindowInner* aWindow,
TrackID aTrackID,
- const MediaTrackConstraints& aConstraints) -> already_AddRefed<PledgeVoid>
+ const MediaTrackConstraints& aConstraints,
+ dom::CallerType aCallerType) -> already_AddRefed<PledgeVoid>
{
MOZ_ASSERT(NS_IsMainThread());
RefPtr<PledgeVoid> p = new PledgeVoid();
// XXX to support multiple tracks of a type in a stream, this should key off
// the TrackID and not just the type
RefPtr<AudioDevice> audioDevice =
aTrackID == kAudioTrack ? mAudioDevice.get() : nullptr;
@@ -3484,17 +3490,17 @@ GetUserMediaCallbackMediaStreamListener:
aTrackID, aTrackID == kAudioTrack ? "audio" : "video"));
p->Resolve(false);
return p.forget();
}
RefPtr<MediaManager> mgr = MediaManager::GetInstance();
uint32_t id = mgr->mOutstandingVoidPledges.Append(*p);
uint64_t windowId = aWindow->WindowID();
- bool isChrome = nsContentUtils::IsCallerChrome();
+ bool isChrome = (aCallerType == dom::CallerType::System);
MediaManager::PostTask(NewTaskFrom([id, windowId,
audioDevice, videoDevice,
aConstraints, isChrome]() mutable {
MOZ_ASSERT(MediaManager::IsInMediaThread());
RefPtr<MediaManager> mgr = MediaManager::GetInstance();
const char* badConstraint = nullptr;
nsresult rv = NS_OK;
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -43,16 +43,17 @@
#include "base/thread.h"
#include "base/task.h"
namespace mozilla {
namespace dom {
struct MediaStreamConstraints;
struct MediaTrackConstraints;
struct MediaTrackConstraintSet;
+enum class CallerType : uint32_t;
} // namespace dom
namespace ipc {
class PrincipalInfo;
}
class MediaManager;
class GetUserMediaCallbackMediaStreamListener;
@@ -236,17 +237,18 @@ public:
// Note: also calls aListener->Remove(), even if inactive
void RemoveFromWindowList(uint64_t aWindowID,
GetUserMediaCallbackMediaStreamListener *aListener);
nsresult GetUserMedia(
nsPIDOMWindowInner* aWindow,
const dom::MediaStreamConstraints& aConstraints,
nsIDOMGetUserMediaSuccessCallback* onSuccess,
- nsIDOMGetUserMediaErrorCallback* onError);
+ nsIDOMGetUserMediaErrorCallback* onError,
+ dom::CallerType aCallerType);
nsresult GetUserMediaDevices(nsPIDOMWindowInner* aWindow,
const dom::MediaStreamConstraints& aConstraints,
nsIGetUserMediaDevicesSuccessCallback* onSuccess,
nsIDOMGetUserMediaErrorCallback* onError,
uint64_t aInnerWindowID = 0,
const nsAString& aCallID = nsString());
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -39,17 +39,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaStreamTrackSource)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
auto
MediaStreamTrackSource::ApplyConstraints(
nsPIDOMWindowInner* aWindow,
- const dom::MediaTrackConstraints& aConstraints) -> already_AddRefed<PledgeVoid>
+ const dom::MediaTrackConstraints& aConstraints,
+ CallerType aCallerType) -> already_AddRefed<PledgeVoid>
{
RefPtr<PledgeVoid> p = new PledgeVoid();
p->Reject(new MediaStreamError(aWindow,
NS_LITERAL_STRING("OverconstrainedError"),
NS_LITERAL_STRING("")));
return p.forget();
}
@@ -265,16 +266,17 @@ MediaStreamTrack::GetConstraints(dom::Me
void
MediaStreamTrack::GetSettings(dom::MediaTrackSettings& aResult)
{
GetSource().GetSettings(aResult);
}
already_AddRefed<Promise>
MediaStreamTrack::ApplyConstraints(const MediaTrackConstraints& aConstraints,
+ CallerType aCallerType,
ErrorResult &aRv)
{
if (MOZ_LOG_TEST(gMediaStreamTrackLog, LogLevel::Info)) {
nsString str;
aConstraints.ToJSON(str);
LOG(LogLevel::Info, ("MediaStreamTrack %p ApplyConstraints() with "
"constraints %s", this, NS_ConvertUTF16toUTF8(str).get()));
@@ -289,17 +291,18 @@ MediaStreamTrack::ApplyConstraints(const
// Forward constraints to the source.
//
// After GetSource().ApplyConstraints succeeds (after it's been to media-thread
// and back), and no sooner, do we set mConstraints to the newly applied values.
// Keep a reference to this, to make sure it's still here when we get back.
RefPtr<MediaStreamTrack> that = this;
- RefPtr<PledgeVoid> p = GetSource().ApplyConstraints(window, aConstraints);
+ RefPtr<PledgeVoid> p = GetSource().ApplyConstraints(window, aConstraints,
+ aCallerType);
p->Then([this, that, promise, aConstraints](bool& aDummy) mutable {
mConstraints = aConstraints;
promise->MaybeResolve(false);
}, [promise](MediaStreamError*& reason) mutable {
promise->MaybeReject(reason);
});
return promise.forget();
}
--- a/dom/media/MediaStreamTrack.h
+++ b/dom/media/MediaStreamTrack.h
@@ -35,16 +35,17 @@ class ProcessedMediaStream;
class RemoteSourceStreamInfo;
class SourceStreamInfo;
namespace dom {
class AudioStreamTrack;
class VideoStreamTrack;
class MediaStreamError;
+enum class CallerType : uint32_t;
/**
* Common interface through which a MediaStreamTrack can communicate with its
* producer on the main thread.
*
* Kept alive by a strong ref in all MediaStreamTracks (original and clones)
* sharing this source.
*/
@@ -118,17 +119,18 @@ public:
typedef media::Pledge<bool, dom::MediaStreamError*> PledgeVoid;
/**
* We provide a fallback solution to ApplyConstraints() here.
* Sources that support ApplyConstraints() will have to override it.
*/
virtual already_AddRefed<PledgeVoid>
ApplyConstraints(nsPIDOMWindowInner* aWindow,
- const dom::MediaTrackConstraints& aConstraints);
+ const dom::MediaTrackConstraints& aConstraints,
+ CallerType aCallerType);
/**
* Same for GetSettings (no-op).
*/
virtual void
GetSettings(dom::MediaTrackSettings& aResult) {};
/**
@@ -283,17 +285,18 @@ public:
void GetLabel(nsAString& aLabel) { GetSource().GetLabel(aLabel); }
bool Enabled() { return mEnabled; }
void SetEnabled(bool aEnabled);
void Stop();
void GetConstraints(dom::MediaTrackConstraints& aResult);
void GetSettings(dom::MediaTrackSettings& aResult);
already_AddRefed<Promise>
- ApplyConstraints(const dom::MediaTrackConstraints& aConstraints, ErrorResult &aRv);
+ ApplyConstraints(const dom::MediaTrackConstraints& aConstraints,
+ CallerType aCallerType, ErrorResult &aRv);
already_AddRefed<MediaStreamTrack> Clone();
MediaStreamTrackState ReadyState() { return mReadyState; }
IMPL_EVENT_HANDLER(ended)
/**
* Convenience (and legacy) method for when ready state is "ended".
*/
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -221,27 +221,27 @@ public:
class AllocationHandle
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AllocationHandle);
protected:
~AllocationHandle() {}
public:
AllocationHandle(const dom::MediaTrackConstraints& aConstraints,
- const ipc::PrincipalInfo& aPrincipalInfo,
+ const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId)
: mConstraints(aConstraints),
mPrincipalInfo(aPrincipalInfo),
mPrefs(aPrefs),
mDeviceId(aDeviceId) {}
public:
NormalizedConstraints mConstraints;
- ipc::PrincipalInfo mPrincipalInfo;
+ mozilla::ipc::PrincipalInfo mPrincipalInfo;
MediaEnginePrefs mPrefs;
nsString mDeviceId;
};
/* Release the device back to the system. */
virtual nsresult Deallocate(AllocationHandle* aHandle)
{
MOZ_ASSERT(aHandle);
--- a/dom/media/webspeech/recognition/SpeechRecognition.cpp
+++ b/dom/media/webspeech/recognition/SpeechRecognition.cpp
@@ -719,17 +719,19 @@ SpeechRecognition::GetServiceURI(nsStrin
void
SpeechRecognition::SetServiceURI(const nsAString& aArg, ErrorResult& aRv)
{
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return;
}
void
-SpeechRecognition::Start(const Optional<NonNull<DOMMediaStream>>& aStream, ErrorResult& aRv)
+SpeechRecognition::Start(const Optional<NonNull<DOMMediaStream>>& aStream,
+ CallerType aCallerType,
+ ErrorResult& aRv)
{
if (mCurrentState != STATE_IDLE) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (!SetRecognitionService(aRv)) {
return;
@@ -751,17 +753,18 @@ SpeechRecognition::Start(const Optional<
if (aStream.WasPassed()) {
StartRecording(&aStream.Value());
} else {
AutoNoJSAPI();
MediaManager* manager = MediaManager::Get();
manager->GetUserMedia(GetOwner(),
constraints,
new GetUserMediaSuccessCallback(this),
- new GetUserMediaErrorCallback(this));
+ new GetUserMediaErrorCallback(this),
+ aCallerType);
}
RefPtr<SpeechEvent> event = new SpeechEvent(this, EVENT_START);
NS_DispatchToMainThread(event);
}
bool
SpeechRecognition::SetRecognitionService(ErrorResult& aRv)
--- a/dom/media/webspeech/recognition/SpeechRecognition.h
+++ b/dom/media/webspeech/recognition/SpeechRecognition.h
@@ -22,16 +22,17 @@
#include "mozilla/WeakPtr.h"
#include "SpeechGrammarList.h"
#include "SpeechRecognitionResultList.h"
#include "SpeechStreamListener.h"
#include "nsISpeechRecognitionService.h"
#include "endpointer.h"
+#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/SpeechRecognitionError.h"
namespace mozilla {
class DOMMediaStream;
namespace dom {
@@ -85,17 +86,18 @@ public:
uint32_t MaxAlternatives() const;
void SetMaxAlternatives(uint32_t aArg);
void GetServiceURI(nsString& aRetVal, ErrorResult& aRv) const;
void SetServiceURI(const nsAString& aArg, ErrorResult& aRv);
- void Start(const Optional<NonNull<DOMMediaStream>>& aStream, ErrorResult& aRv);
+ void Start(const Optional<NonNull<DOMMediaStream>>& aStream,
+ CallerType aCallerType, ErrorResult& aRv);
void Stop();
void Abort();
IMPL_EVENT_HANDLER(audiostart)
IMPL_EVENT_HANDLER(soundstart)
IMPL_EVENT_HANDLER(speechstart)
--- a/dom/presentation/provider/AndroidCastDeviceProvider.js
+++ b/dom/presentation/provider/AndroidCastDeviceProvider.js
@@ -8,17 +8,17 @@
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
// globals XPCOMUtils
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
// globals Services
Cu.import("resource://gre/modules/Services.jsm");
-// globals Messaging
+// globals EventDispatcher
Cu.import("resource://gre/modules/Messaging.jsm");
function log(str) {
// dump("-*- AndroidCastDeviceProvider -*-: " + str + "\n");
}
// Helper function: transfer nsIPresentationChannelDescription to json
function descriptionToString(aDescription) {
@@ -296,17 +296,17 @@ ChromecastRemoteDisplayDevice.prototype
this._role);
if (this._role == Ci.nsIPresentationService.ROLE_CONTROLLER) {
// Only connect to Chromecast for controller.
// Monitor the receiver being ready.
Services.obs.addObserver(this, TOPIC_PRESENTATION_VIEW_READY, true);
// Launch Chromecast service in Android.
- Messaging.sendRequestForResult({
+ EventDispatcher.instance.sendRequestForResult({
type: TOPIC_ANDROID_CAST_DEVICE_START,
id: this.id
}).then(result => {
log("Chromecast is connected.");
}).catch(error => {
log("Can not connect to Chromecast.");
// If Chromecast can not be launched, remove the observer.
Services.obs.removeObserver(this, TOPIC_PRESENTATION_VIEW_READY);
@@ -318,17 +318,17 @@ ChromecastRemoteDisplayDevice.prototype
this._ctrlChannel.notifyConnected();
}
return this._ctrlChannel;
},
disconnect: function CRDD_disconnect() {
// Disconnect from Chromecast.
- Messaging.sendRequestForResult({
+ EventDispatcher.instance.sendRequestForResult({
type: TOPIC_ANDROID_CAST_DEVICE_STOP,
id: this.id
});
},
isRequestedUrlSupported: function CRDD_isRequestedUrlSupported(aUrl) {
let url = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService)
@@ -405,17 +405,17 @@ AndroidCastDeviceProvider.prototype = {
// remove observer
Services.obs.removeObserver(this, TOPIC_ANDROID_CAST_DEVICE_ADDED);
Services.obs.removeObserver(this, TOPIC_ANDROID_CAST_DEVICE_CHANGED);
Services.obs.removeObserver(this, TOPIC_ANDROID_CAST_DEVICE_REMOVED);
return;
}
// Sync all device already found by Android.
- Messaging.sendRequest({ type: TOPIC_ANDROID_CAST_DEVICE_SYNCDEVICE });
+ EventDispatcher.instance.sendRequest({ type: TOPIC_ANDROID_CAST_DEVICE_SYNCDEVICE });
// Observer registration
Services.obs.addObserver(this, TOPIC_ANDROID_CAST_DEVICE_ADDED, false);
Services.obs.addObserver(this, TOPIC_ANDROID_CAST_DEVICE_CHANGED, false);
Services.obs.addObserver(this, TOPIC_ANDROID_CAST_DEVICE_REMOVED, false);
},
get listener() {
return this._listener;
--- a/dom/push/PushRecord.jsm
+++ b/dom/push/PushRecord.jsm
@@ -9,17 +9,17 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
+XPCOMUtils.defineLazyModuleGetter(this, "EventDispatcher",
"resource://gre/modules/Messaging.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
@@ -141,17 +141,17 @@ PushRecord.prototype = {
getLastVisit: Task.async(function* () {
if (!this.quotaApplies() || this.isTabOpen()) {
// If the registration isn't subject to quota, or the user already
// has the site open, skip expensive database queries.
return Date.now();
}
if (AppConstants.MOZ_ANDROID_HISTORY) {
- let result = yield Messaging.sendRequestForResult({
+ let result = yield EventDispatcher.instance.sendRequestForResult({
type: "History:GetPrePathLastVisitedTimeMilliseconds",
prePath: this.uri.prePath,
});
return result == 0 ? -Infinity : result;
}
// Places History transition types that can fire a
// `pushsubscriptionchange` event when the user visits a site with expired push
--- a/dom/push/PushServiceAndroidGCM.jsm
+++ b/dom/push/PushServiceAndroidGCM.jsm
@@ -8,17 +8,17 @@
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm");
const {PushRecord} = Cu.import("resource://gre/modules/PushRecord.jsm");
const {PushCrypto} = Cu.import("resource://gre/modules/PushCrypto.jsm");
-Cu.import("resource://gre/modules/Messaging.jsm"); /*global: Messaging */
+Cu.import("resource://gre/modules/Messaging.jsm"); /*global: EventDispatcher */
Cu.import("resource://gre/modules/Services.jsm"); /*global: Services */
Cu.import("resource://gre/modules/Preferences.jsm"); /*global: Preferences */
Cu.import("resource://gre/modules/Promise.jsm"); /*global: Promise */
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global: XPCOMUtils */
const Log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bind("Push");
this.EXPORTED_SYMBOLS = ["PushServiceAndroidGCM"];
@@ -131,41 +131,41 @@ this.PushServiceAndroidGCM = {
// The Push server may append padding.
padding: "ignore",
});
}
return { headers, message };
},
_configure: function(serverURL, debug) {
- return Messaging.sendRequestForResult({
+ return EventDispatcher.instance.sendRequestForResult({
type: "PushServiceAndroidGCM:Configure",
endpoint: serverURL.spec,
debug: debug,
});
},
init: function(options, mainPushService, serverURL) {
console.debug("init()");
this._mainPushService = mainPushService;
this._serverURI = serverURL;
prefs.observe("debug", this);
Services.obs.addObserver(this, "PushServiceAndroidGCM:ReceivedPushMessage", false);
return this._configure(serverURL, !!prefs.get("debug")).then(() => {
- Messaging.sendRequestForResult({
+ EventDispatcher.instance.sendRequestForResult({
type: "PushServiceAndroidGCM:Initialized"
});
});
},
uninit: function() {
console.debug("uninit()");
- Messaging.sendRequestForResult({
+ EventDispatcher.instance.sendRequestForResult({
type: "PushServiceAndroidGCM:Uninitialized"
});
this._mainPushService = null;
Services.obs.removeObserver(this, "PushServiceAndroidGCM:ReceivedPushMessage");
prefs.ignore("debug", this);
},
@@ -173,17 +173,17 @@ this.PushServiceAndroidGCM = {
// No action required.
},
connect: function(records) {
console.debug("connect:", records);
// It's possible for the registration or subscriptions backing the
// PushService to not be registered with the underlying AndroidPushService.
// Expire those that are unrecognized.
- return Messaging.sendRequestForResult({
+ return EventDispatcher.instance.sendRequestForResult({
type: "PushServiceAndroidGCM:DumpSubscriptions",
})
.then(subscriptions => {
console.debug("connect:", subscriptions);
// subscriptions maps chid => subscription data.
return Promise.all(records.map(record => {
if (subscriptions.hasOwnProperty(record.keyID)) {
console.debug("connect:", "hasOwnProperty", record.keyID);
@@ -218,17 +218,17 @@ this.PushServiceAndroidGCM = {
let message = {
type: "PushServiceAndroidGCM:SubscribeChannel",
appServerKey: appServerKey,
}
if (record.scope == FXA_PUSH_SCOPE) {
message.service = "fxa";
}
// Caller handles errors.
- return Messaging.sendRequestForResult(message)
+ return EventDispatcher.instance.sendRequestForResult(message)
.then(data => {
console.debug("Got data:", data);
return PushCrypto.generateKeys()
.then(exportedKeys =>
new PushRecordAndroidGCM({
// Straight from autopush.
channelID: data.channelID,
pushEndpoint: data.endpoint,
@@ -244,17 +244,17 @@ this.PushServiceAndroidGCM = {
appServerKey: record.appServerKey,
})
);
});
},
unregister: function(record) {
console.debug("unregister: ", record);
- return Messaging.sendRequestForResult({
+ return EventDispatcher.instance.sendRequestForResult({
type: "PushServiceAndroidGCM:UnsubscribeChannel",
channelID: record.keyID,
});
},
reportDeliveryError: function(messageID, reason) {
console.warn("reportDeliveryError: Ignoring message delivery error",
messageID, reason);
--- a/dom/quota/QuotaManagerService.cpp
+++ b/dom/quota/QuotaManagerService.cpp
@@ -535,17 +535,16 @@ QuotaManagerService::GetUsageForPrincipa
request.forget(_retval);
return NS_OK;
}
NS_IMETHODIMP
QuotaManagerService::Clear(nsIQuotaRequest** _retval)
{
MOZ_ASSERT(NS_IsMainThread());
- MOZ_ASSERT(nsContentUtils::IsCallerChrome());
if (NS_WARN_IF(!gTestingMode)) {
return NS_ERROR_UNEXPECTED;
}
RefPtr<Request> request = new Request();
ClearAllParams params;
@@ -564,17 +563,16 @@ QuotaManagerService::Clear(nsIQuotaReque
NS_IMETHODIMP
QuotaManagerService::ClearStoragesForPrincipal(nsIPrincipal* aPrincipal,
const nsACString& aPersistenceType,
bool aClearAll,
nsIQuotaRequest** _retval)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aPrincipal);
- MOZ_ASSERT(nsContentUtils::IsCallerChrome());
nsCString suffix;
aPrincipal->OriginAttributesRef().CreateSuffix(suffix);
if (NS_WARN_IF(aClearAll && !suffix.IsEmpty())) {
// The originAttributes should be default originAttributes when the
// aClearAll flag is set.
return NS_ERROR_INVALID_ARG;
@@ -621,17 +619,16 @@ QuotaManagerService::ClearStoragesForPri
request.forget(_retval);
return NS_OK;
}
NS_IMETHODIMP
QuotaManagerService::Reset(nsIQuotaRequest** _retval)
{
MOZ_ASSERT(NS_IsMainThread());
- MOZ_ASSERT(nsContentUtils::IsCallerChrome());
if (NS_WARN_IF(!gTestingMode)) {
return NS_ERROR_UNEXPECTED;
}
RefPtr<Request> request = new Request();
ResetAllParams params;
--- a/dom/webidl/Cache.webidl
+++ b/dom/webidl/Cache.webidl
@@ -12,19 +12,19 @@
[Exposed=(Window,Worker),
Func="mozilla::dom::cache::Cache::PrefEnabled"]
interface Cache {
[NewObject]
Promise<Response> match(RequestInfo request, optional CacheQueryOptions options);
[NewObject]
Promise<sequence<Response>> matchAll(optional RequestInfo request, optional CacheQueryOptions options);
- [NewObject]
+ [NewObject, NeedsCallerType]
Promise<void> add(RequestInfo request);
- [NewObject]
+ [NewObject, NeedsCallerType]
Promise<void> addAll(sequence<RequestInfo> requests);
[NewObject]
Promise<void> put(RequestInfo request, Response response);
[NewObject]
Promise<boolean> delete(RequestInfo request, optional CacheQueryOptions options);
[NewObject]
Promise<sequence<Request>> keys(optional RequestInfo request, optional CacheQueryOptions options);
};
--- a/dom/webidl/CanvasRenderingContext2D.webidl
+++ b/dom/webidl/CanvasRenderingContext2D.webidl
@@ -105,20 +105,16 @@ interface CanvasRenderingContext2D {
* transparency.
*
* This API cannot currently be used by Web content. It is chrome
* and Web Extensions (with a permission) only.
*/
[Throws, Func="CanvasUtils::HasDrawWindowPrivilege"]
void drawWindow(Window window, double x, double y, double w, double h,
DOMString bgColor, optional unsigned long flags = 0);
- [Throws, ChromeOnly]
- void asyncDrawXULElement(XULElement elem, double x, double y, double w,
- double h, DOMString bgColor,
- optional unsigned long flags = 0);
/**
* This causes a context that is currently using a hardware-accelerated
* backend to fallback to a software one. All state should be preserved.
*/
[ChromeOnly]
void demote();
};
--- a/dom/webidl/File.webidl
+++ b/dom/webidl/File.webidl
@@ -31,19 +31,19 @@ dictionary ChromeFilePropertyBag : FileP
// Mozilla extensions
partial interface File {
[GetterThrows, Deprecated="FileLastModifiedDate"]
readonly attribute Date lastModifiedDate;
[BinaryName="path", Func="mozilla::dom::Directory::WebkitBlinkDirectoryPickerEnabled"]
readonly attribute USVString webkitRelativePath;
- [GetterThrows, ChromeOnly]
+ [GetterThrows, ChromeOnly, NeedsCallerType]
readonly attribute DOMString mozFullPath;
- [ChromeOnly, Throws]
+ [ChromeOnly, Throws, NeedsCallerType]
static File createFromNsIFile(nsIFile file,
optional ChromeFilePropertyBag options);
- [ChromeOnly, Throws]
+ [ChromeOnly, Throws, NeedsCallerType]
static File createFromFileName(USVString fileName,
optional ChromeFilePropertyBag options);
};
--- a/dom/webidl/GeometryUtils.webidl
+++ b/dom/webidl/GeometryUtils.webidl
@@ -18,21 +18,21 @@ dictionary BoxQuadOptions {
dictionary ConvertCoordinateOptions {
CSSBoxType fromBox = "border";
CSSBoxType toBox = "border";
};
[NoInterfaceObject]
interface GeometryUtils {
- [Throws, Func="nsINode::HasBoxQuadsSupport"]
+ [Throws, Func="nsINode::HasBoxQuadsSupport", NeedsCallerType]
sequence<DOMQuad> getBoxQuads(optional BoxQuadOptions options);
- [Throws, Pref="layout.css.convertFromNode.enabled"]
+ [Throws, Pref="layout.css.convertFromNode.enabled", NeedsCallerType]
DOMQuad convertQuadFromNode(DOMQuad quad, GeometryNode from, optional ConvertCoordinateOptions options);
- [Throws, Pref="layout.css.convertFromNode.enabled"]
+ [Throws, Pref="layout.css.convertFromNode.enabled", NeedsCallerType]
DOMQuad convertRectFromNode(DOMRectReadOnly rect, GeometryNode from, optional ConvertCoordinateOptions options);
- [Throws, Pref="layout.css.convertFromNode.enabled"]
+ [Throws, Pref="layout.css.convertFromNode.enabled", NeedsCallerType]
DOMPoint convertPointFromNode(DOMPointInit point, GeometryNode from, optional ConvertCoordinateOptions options);
};
// PseudoElement implements GeometryUtils;
typedef (Text or Element /* or PseudoElement */ or Document) GeometryNode;
--- a/dom/webidl/HTMLCanvasElement.webidl
+++ b/dom/webidl/HTMLCanvasElement.webidl
@@ -63,9 +63,9 @@ interface MozCanvasPrintState
readonly attribute nsISupports context;
// To be called when rendering to the context is done.
void done();
};
callback PrintCallback = void(MozCanvasPrintState ctx);
-callback BlobCallback = void(Blob blob);
+callback BlobCallback = void(Blob? blob);
--- a/dom/webidl/HTMLDocument.webidl
+++ b/dom/webidl/HTMLDocument.webidl
@@ -40,20 +40,20 @@ interface HTMLDocument : Document {
void close();
[Throws]
void write(DOMString... text);
[Throws]
void writeln(DOMString... text);
[SetterThrows, NeedsSubjectPrincipal]
attribute DOMString designMode;
- [Throws, NeedsCallerType]
+ [Throws, NeedsSubjectPrincipal]
boolean execCommand(DOMString commandId, optional boolean showUI = false,
optional DOMString value = "");
- [Throws, NeedsCallerType]
+ [Throws, NeedsSubjectPrincipal]
boolean queryCommandEnabled(DOMString commandId);
[Throws]
boolean queryCommandIndeterm(DOMString commandId);
[Throws]
boolean queryCommandState(DOMString commandId);
[NeedsCallerType]
boolean queryCommandSupported(DOMString commandId);
[Throws]
--- a/dom/webidl/HTMLObjectElement.webidl
+++ b/dom/webidl/HTMLObjectElement.webidl
@@ -159,17 +159,17 @@ interface MozObjectLoadingContent {
[ChromeOnly]
sequence<MozPluginParameter> getPluginParameters();
/**
* This method will play a plugin that has been stopped by the click-to-play
* feature.
*/
- [ChromeOnly, Throws]
+ [ChromeOnly, Throws, NeedsCallerType]
void playPlugin();
/**
* Forces a re-evaluation and reload of the tag, optionally invalidating its
* click-to-play state. This can be used when the MIME type that provides a
* type has changed, for instance, to force the tag to re-evalulate the
* handler to use.
*/
@@ -199,17 +199,17 @@ interface MozObjectLoadingContent {
/**
* If this object currently owns a running plugin, regardless of whether or
* not one is pending spawn/despawn.
*/
[ChromeOnly]
readonly attribute boolean hasRunningPlugin;
- [ChromeOnly, Throws]
+ [ChromeOnly, Throws, NeedsCallerType]
readonly attribute unsigned long runID;
};
/**
* Name:Value pair type used for passing parameters to NPAPI or javascript
* plugins.
*/
dictionary MozPluginParameter {
--- a/dom/webidl/IDBFactory.webidl
+++ b/dom/webidl/IDBFactory.webidl
@@ -20,46 +20,46 @@ dictionary IDBOpenDBOptions
/**
* Interface that defines the indexedDB property on a window. See
* http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBFactory
* for more information.
*/
[Exposed=(Window,Worker,System)]
interface IDBFactory {
- [Throws]
+ [Throws, NeedsCallerType]
IDBOpenDBRequest
open(DOMString name,
[EnforceRange] unsigned long long version);
- [Throws]
+ [Throws, NeedsCallerType]
IDBOpenDBRequest
open(DOMString name,
optional IDBOpenDBOptions options);
- [Throws]
+ [Throws, NeedsCallerType]
IDBOpenDBRequest
deleteDatabase(DOMString name,
optional IDBOpenDBOptions options);
[Throws]
short
cmp(any first,
any second);
- [Throws, ChromeOnly]
+ [Throws, ChromeOnly, NeedsCallerType]
IDBOpenDBRequest
openForPrincipal(Principal principal,
DOMString name,
[EnforceRange] unsigned long long version);
- [Throws, ChromeOnly]
+ [Throws, ChromeOnly, NeedsCallerType]
IDBOpenDBRequest
openForPrincipal(Principal principal,
DOMString name,
optional IDBOpenDBOptions options);
- [Throws, ChromeOnly]
+ [Throws, ChromeOnly, NeedsCallerType]
IDBOpenDBRequest
deleteForPrincipal(Principal principal,
DOMString name,
optional IDBOpenDBOptions options);
};
--- a/dom/webidl/MediaDevices.webidl
+++ b/dom/webidl/MediaDevices.webidl
@@ -14,11 +14,11 @@
interface MediaDevices : EventTarget {
[Pref="media.ondevicechange.enabled"]
attribute EventHandler ondevicechange;
MediaTrackSupportedConstraints getSupportedConstraints();
[Throws]
Promise<sequence<MediaDeviceInfo>> enumerateDevices();
- [Throws]
+ [Throws, NeedsCallerType]
Promise<MediaStream> getUserMedia(optional MediaStreamConstraints constraints);
};
--- a/dom/webidl/MediaStreamTrack.webidl
+++ b/dom/webidl/MediaStreamTrack.webidl
@@ -82,12 +82,12 @@ interface MediaStreamTrack : EventTarget
readonly attribute MediaStreamTrackState readyState;
attribute EventHandler onended;
MediaStreamTrack clone ();
void stop ();
// MediaTrackCapabilities getCapabilities ();
MediaTrackConstraints getConstraints ();
MediaTrackSettings getSettings ();
- [Throws]
+ [Throws, NeedsCallerType]
Promise<void> applyConstraints (optional MediaTrackConstraints constraints);
// attribute EventHandler onoverconstrained;
};
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -310,17 +310,18 @@ callback NavigatorUserMediaSuccessCallba
callback NavigatorUserMediaErrorCallback = void (MediaStreamError error);
partial interface Navigator {
[Throws, Func="Navigator::HasUserMediaSupport"]
readonly attribute MediaDevices mediaDevices;
// Deprecated. Use mediaDevices.getUserMedia instead.
[Deprecated="NavigatorGetUserMedia", Throws,
- Func="Navigator::HasUserMediaSupport", UnsafeInPrerendering]
+ Func="Navigator::HasUserMediaSupport", UnsafeInPrerendering,
+ NeedsCallerType]
void mozGetUserMedia(MediaStreamConstraints constraints,
NavigatorUserMediaSuccessCallback successCallback,
NavigatorUserMediaErrorCallback errorCallback);
};
// nsINavigatorUserMedia
callback MozGetUserMediaDevicesSuccessCallback = void (nsIVariant? devices);
partial interface Navigator {
--- a/dom/webidl/Node.webidl
+++ b/dom/webidl/Node.webidl
@@ -26,17 +26,17 @@ interface Node : EventTarget {
const unsigned short DOCUMENT_TYPE_NODE = 10;
const unsigned short DOCUMENT_FRAGMENT_NODE = 11;
const unsigned short NOTATION_NODE = 12; // historical
[Constant]
readonly attribute unsigned short nodeType;
[Pure]
readonly attribute DOMString nodeName;
- [Pure, Throws]
+ [Pure, Throws, NeedsCallerType, BinaryName="baseURIFromJS"]
readonly attribute DOMString? baseURI;
[Pure, BinaryName=getComposedDoc]
readonly attribute boolean isConnected;
[Pure]
readonly attribute Document? ownerDocument;
[Pure]
Node getRootNode(optional GetRootNodeOptions options);
--- a/dom/webidl/SpeechRecognition.webidl
+++ b/dom/webidl/SpeechRecognition.webidl
@@ -20,17 +20,17 @@ interface SpeechRecognition : EventTarge
[Throws]
attribute boolean continuous;
attribute boolean interimResults;
attribute unsigned long maxAlternatives;
[Throws]
attribute DOMString serviceURI;
// methods to drive the speech interaction
- [Throws, UnsafeInPrerendering]
+ [Throws, UnsafeInPrerendering, NeedsCallerType]
void start(optional MediaStream stream);
void stop();
void abort();
// event methods
attribute EventHandler onaudiostart;
attribute EventHandler onsoundstart;
attribute EventHandler onspeechstart;
--- a/dom/webidl/TestFunctions.webidl
+++ b/dom/webidl/TestFunctions.webidl
@@ -3,21 +3,39 @@
* 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/.
*/
// A dumping ground for random testing functions
callback PromiseReturner = Promise<any>();
-[Pref="dom.expose_test_interfaces"]
+[Pref="dom.expose_test_interfaces",
+ Constructor]
interface TestFunctions {
[Throws]
static void throwUncatchableException();
// Simply returns its argument. Can be used to test Promise
// argument processing behavior.
static Promise<any> passThroughPromise(Promise<any> arg);
// Returns whatever Promise the given PromiseReturner returned.
[Throws]
static Promise<any> passThroughCallbackPromise(PromiseReturner callback);
+
+ // Some basic tests for string binding round-tripping behavior.
+ void setStringData(DOMString arg);
+
+ // Get the string data, using an nsAString argument on the C++ side.
+ // This will just use Assign/operator=, whatever that does.
+ DOMString getStringDataAsAString();
+
+ // Get the string data, but only "length" chars of it, using an
+ // nsAString argument on the C++ side. This will always copy on the
+ // C++ side.
+ DOMString getStringDataAsAString(unsigned long length);
+
+ // Get the string data, but only "length" chars of it, using a
+ // DOMString argument on the C++ side and trying to hand it
+ // stringbuffers. If length not passed, use our full length.
+ DOMString getStringDataAsDOMString(optional unsigned long length);
};
--- a/dom/webidl/WebGL2RenderingContext.webidl
+++ b/dom/webidl/WebGL2RenderingContext.webidl
@@ -606,24 +606,24 @@ interface WebGL2RenderingContextBase
/* Writing to the drawing buffer */
void vertexAttribDivisor(GLuint index, GLuint divisor);
void drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instanceCount);
void drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, GLintptr offset, GLsizei instanceCount);
void drawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, GLintptr offset);
/* Reading back pixels */
// WebGL1:
- [Throws] // Throws on readback in a write-only context.
+ [Throws, NeedsCallerType] // Throws on readback in a write-only context.
void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
ArrayBufferView? dstData);
// WebGL2:
- [Throws] // Throws on readback in a write-only context.
+ [Throws, NeedsCallerType] // Throws on readback in a write-only context.
void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
GLintptr offset);
- [Throws] // Throws on readback in a write-only context.
+ [Throws, NeedsCallerType] // Throws on readback in a write-only context.
void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
ArrayBufferView dstData, GLuint dstOffset);
/* Multiple Render Targets */
void drawBuffers(sequence<GLenum> buffers);
void clearBufferfv(GLenum buffer, GLint drawbuffer, Float32List values,
optional GLuint srcOffset = 0);
--- a/dom/webidl/WebGLRenderingContext.webidl
+++ b/dom/webidl/WebGLRenderingContext.webidl
@@ -723,17 +723,17 @@ interface WebGLRenderingContext {
ArrayBufferView data);
// compressedTexSubImage2D has WebGL2 overloads.
void compressedTexSubImage2D(GLenum target, GLint level,
GLint xoffset, GLint yoffset,
GLsizei width, GLsizei height, GLenum format,
ArrayBufferView data);
// readPixels has WebGL2 overloads.
- [Throws]
+ [Throws, NeedsCallerType]
void readPixels(GLint x, GLint y, GLsizei width, GLsizei height,
GLenum format, GLenum type, ArrayBufferView? pixels);
// texImage2D has WebGL2 overloads.
// Overloads must share [Throws].
[Throws] // Can't actually throw.
void texImage2D(GLenum target, GLint level, GLint internalformat,
GLsizei width, GLsizei height, GLint border, GLenum format,
--- a/dom/webidl/WindowOrWorkerGlobalScope.webidl
+++ b/dom/webidl/WindowOrWorkerGlobalScope.webidl
@@ -41,17 +41,18 @@ interface WindowOrWorkerGlobalScope {
[Throws]
Promise<ImageBitmap> createImageBitmap(ImageBitmapSource aImage);
[Throws]
Promise<ImageBitmap> createImageBitmap(ImageBitmapSource aImage, long aSx, long aSy, long aSw, long aSh);
};
// https://fetch.spec.whatwg.org/#fetch-method
partial interface WindowOrWorkerGlobalScope {
- [NewObject] Promise<Response> fetch(RequestInfo input, optional RequestInit init);
+ [NewObject, NeedsCallerType]
+ Promise<Response> fetch(RequestInfo input, optional RequestInit init);
};
// https://w3c.github.io/webappsec-secure-contexts/#monkey-patching-global-object
partial interface WindowOrWorkerGlobalScope {
readonly attribute boolean isSecureContext;
};
// http://w3c.github.io/IndexedDB/#factory-interface
--- a/dom/webidl/Worklet.webidl
+++ b/dom/webidl/Worklet.webidl
@@ -4,11 +4,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* https://drafts.css-houdini.org/worklets/#idl-index
*/
[Pref="dom.worklet.enabled"]
interface Worklet {
- [NewObject, Throws]
+ [NewObject, Throws, NeedsCallerType]
Promise<void> import(USVString moduleURL);
};
--- a/dom/webidl/XMLDocument.webidl
+++ b/dom/webidl/XMLDocument.webidl
@@ -8,16 +8,16 @@
* http://www.whatwg.org/specs/web-apps/current-work/#xmldocument
*/
// http://dom.spec.whatwg.org/#xmldocument
interface XMLDocument : Document {};
// http://www.whatwg.org/specs/web-apps/current-work/#xmldocument
partial interface XMLDocument {
- [Throws]
+ [Throws, NeedsCallerType]
boolean load(DOMString url);
};
// Gecko extensions?
partial interface XMLDocument {
attribute boolean async;
};
--- a/dom/webidl/XSLTProcessor.webidl
+++ b/dom/webidl/XSLTProcessor.webidl
@@ -99,11 +99,11 @@ interface XSLTProcessor {
*/
[ChromeOnly]
const unsigned long DISABLE_ALL_LOADS = 1;
/**
* Flags for this processor. Defaults to 0. See individual flags above
* for documentation for effect of reset()
*/
- [ChromeOnly]
+ [ChromeOnly, NeedsCallerType]
attribute unsigned long flags;
};
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4270,25 +4270,25 @@ WorkerPrivate::Constructor(const GlobalO
{
return WorkerPrivate::Constructor(aGlobal, aScriptURL, false,
WorkerTypeDedicated, EmptyCString(),
nullptr, aRv);
}
// static
bool
-WorkerPrivate::WorkerAvailable(JSContext* /* unused */, JSObject* /* unused */)
+WorkerPrivate::WorkerAvailable(JSContext* aCx, JSObject* /* unused */)
{
// If we're already on a worker workers are clearly enabled.
if (!NS_IsMainThread()) {
return true;
}
// If our caller is chrome, workers are always available.
- if (nsContentUtils::IsCallerChrome()) {
+ if (nsContentUtils::IsSystemCaller(aCx)) {
return true;
}
// Else check the pref.
return Preferences::GetBool(PREF_WORKERS_ENABLED);
}
// static
@@ -4307,17 +4307,17 @@ ChromeWorkerPrivate::Constructor(const G
bool
ChromeWorkerPrivate::WorkerAvailable(JSContext* aCx, JSObject* /* unused */)
{
// Chrome is always allowed to use workers, and content is never
// allowed to use ChromeWorker, so all we have to check is the
// caller. However, chrome workers apparently might not have a
// system principal, so we have to check for them manually.
if (NS_IsMainThread()) {
- return nsContentUtils::IsCallerChrome();
+ return nsContentUtils::IsSystemCaller(aCx);
}
return GetWorkerPrivateFromContext(aCx)->IsChromeWorker();
}
// static
already_AddRefed<WorkerPrivate>
WorkerPrivate::Constructor(const GlobalObject& aGlobal,
@@ -4488,17 +4488,17 @@ WorkerPrivate::GetLoadInfo(JSContext* aC
AssertIsOnMainThread();
// Make sure that the IndexedDatabaseManager is set up
Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
MOZ_ASSERT(ssm);
- bool isChrome = nsContentUtils::IsCallerChrome();
+ bool isChrome = nsContentUtils::IsSystemCaller(aCx);
// First check to make sure the caller has permission to make a privileged
// worker if they called the ChromeWorker/ChromeSharedWorker constructor.
if (aIsChromeWorker && !isChrome) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// Chrome callers (whether creating a ChromeWorker or Worker) always get the
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -395,19 +395,20 @@ WorkerGlobalScope::GetPerformance()
mPerformance = Performance::CreateForWorker(mWorkerPrivate);
}
return mPerformance;
}
already_AddRefed<Promise>
WorkerGlobalScope::Fetch(const RequestOrUSVString& aInput,
- const RequestInit& aInit, ErrorResult& aRv)
+ const RequestInit& aInit,
+ CallerType aCallerType, ErrorResult& aRv)
{
- return FetchRequest(this, aInput, aInit, aRv);
+ return FetchRequest(this, aInput, aInit, aCallerType, aRv);
}
already_AddRefed<IDBFactory>
WorkerGlobalScope::GetIndexedDB(ErrorResult& aErrorResult)
{
mWorkerPrivate->AssertIsOnWorkerThread();
RefPtr<IDBFactory> indexedDB = mIndexedDB;
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -25,16 +25,17 @@ class Function;
class IDBFactory;
enum class ImageBitmapFormat : uint8_t;
class Performance;
class Promise;
class RequestOrUSVString;
class ServiceWorkerRegistration;
class WorkerLocation;
class WorkerNavigator;
+enum class CallerType : uint32_t;
namespace cache {
class CacheStorage;
} // namespace cache
namespace workers {
@@ -150,17 +151,18 @@ public:
IMPL_EVENT_HANDLER(offline)
void
Dump(const Optional<nsAString>& aString) const;
Performance* GetPerformance();
already_AddRefed<Promise>
- Fetch(const RequestOrUSVString& aInput, const RequestInit& aInit, ErrorResult& aRv);
+ Fetch(const RequestOrUSVString& aInput, const RequestInit& aInit,
+ CallerType aCallerType, ErrorResult& aRv);
already_AddRefed<IDBFactory>
GetIndexedDB(ErrorResult& aErrorResult);
already_AddRefed<cache::CacheStorage>
GetCaches(ErrorResult& aRv);
bool IsSecureContext() const;
--- a/dom/worklet/Worklet.cpp
+++ b/dom/worklet/Worklet.cpp
@@ -28,17 +28,18 @@ namespace dom {
class WorkletFetchHandler : public PromiseNativeHandler
, public nsIStreamLoaderObserver
{
public:
NS_DECL_ISUPPORTS
static already_AddRefed<Promise>
- Fetch(Worklet* aWorklet, const nsAString& aModuleURL, ErrorResult& aRv)
+ Fetch(Worklet* aWorklet, const nsAString& aModuleURL, CallerType aCallerType,
+ ErrorResult& aRv)
{
MOZ_ASSERT(aWorklet);
nsCOMPtr<nsIGlobalObject> global =
do_QueryInterface(aWorklet->GetParentObject());
MOZ_ASSERT(global);
RefPtr<Promise> promise = Promise::Create(global, aRv);
@@ -80,17 +81,18 @@ public:
}
}
RequestOrUSVString request;
request.SetAsUSVString().Rebind(aModuleURL.Data(), aModuleURL.Length());
RequestInit init;
- RefPtr<Promise> fetchPromise = FetchRequest(global, request, init, aRv);
+ RefPtr<Promise> fetchPromise =
+ FetchRequest(global, request, init, aCallerType, aRv);
if (NS_WARN_IF(aRv.Failed())) {
promise->MaybeReject(aRv);
return promise.forget();
}
RefPtr<WorkletFetchHandler> handler =
new WorkletFetchHandler(aWorklet, aModuleURL, promise);
fetchPromise->AppendNativeHandler(handler);
@@ -345,19 +347,20 @@ Worklet::~Worklet()
JSObject*
Worklet::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return WorkletBinding::Wrap(aCx, this, aGivenProto);
}
already_AddRefed<Promise>
-Worklet::Import(const nsAString& aModuleURL, ErrorResult& aRv)
+Worklet::Import(const nsAString& aModuleURL, CallerType aCallerType,
+ ErrorResult& aRv)
{
- return WorkletFetchHandler::Fetch(this, aModuleURL, aRv);
+ return WorkletFetchHandler::Fetch(this, aModuleURL, aCallerType, aRv);
}
WorkletGlobalScope*
Worklet::GetOrCreateGlobalScope(JSContext* aCx)
{
if (!mScope) {
switch (mWorkletType) {
case eAudioWorklet:
--- a/dom/worklet/Worklet.h
+++ b/dom/worklet/Worklet.h
@@ -17,16 +17,17 @@ class nsPIDOMWindowInner;
class nsIPrincipal;
namespace mozilla {
namespace dom {
class Promise;
class WorkletGlobalScope;
class WorkletFetchHandler;
+enum class CallerType : uint32_t;
class Worklet final : public nsISupports
, public nsWrapperCache
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Worklet)
@@ -42,17 +43,18 @@ public:
{
return mWindow;
}
virtual JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
already_AddRefed<Promise>
- Import(const nsAString& aModuleURL, ErrorResult& aRv);
+ Import(const nsAString& aModuleURL, CallerType aCallerType,
+ ErrorResult& aRv);
WorkletGlobalScope*
GetOrCreateGlobalScope(JSContext* aCx);
private:
~Worklet();
WorkletFetchHandler*
--- a/dom/xml/XMLDocument.cpp
+++ b/dom/xml/XMLDocument.cpp
@@ -272,17 +272,18 @@ XMLDocument::ResetToURI(nsIURI *aURI, ns
mChannel->Cancel(NS_BINDING_ABORTED);
mChannelIsPending = false;
}
nsDocument::ResetToURI(aURI, aLoadGroup, aPrincipal);
}
bool
-XMLDocument::Load(const nsAString& aUrl, ErrorResult& aRv)
+XMLDocument::Load(const nsAString& aUrl, CallerType aCallerType,
+ ErrorResult& aRv)
{
bool hasHadScriptObject = true;
nsIScriptGlobalObject* scriptObject =
GetScriptHandlingObject(hasHadScriptObject);
if (!scriptObject && hasHadScriptObject) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return false;
}
@@ -303,17 +304,17 @@ XMLDocument::Load(const nsAString& aUrl,
// Reporting a warning on ourselves is rather pointless, because we probably
// have no window id (and hence the warning won't show up in any web console)
// and probably aren't considered a "content document" because we're not
// loaded in a docshell, so won't accumulate telemetry for use counters. Try
// warning on our entry document, if any, since that should have things like
// window ids and associated docshells.
nsIDocument* docForWarning = callingDoc ? callingDoc.get() : this;
- if (nsContentUtils::IsCallerChrome()) {
+ if (aCallerType == CallerType::System) {
docForWarning->WarnOnceAbout(nsIDocument::eChromeUseOfDOM3LoadMethod);
} else {
docForWarning->WarnOnceAbout(nsIDocument::eUseOfDOM3LoadMethod);
}
nsIURI *baseURI = mDocumentURI;
nsAutoCString charset;
--- a/dom/xml/XMLDocument.h
+++ b/dom/xml/XMLDocument.h
@@ -3,16 +3,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_XMLDocument_h
#define mozilla_dom_XMLDocument_h
#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
#include "nsDocument.h"
#include "nsIDOMXMLDocument.h"
#include "nsIScriptContext.h"
class nsIURI;
class nsIChannel;
namespace mozilla {
@@ -52,17 +53,17 @@ public:
virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
virtual void DocAddSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const override;
// DocAddSizeOfIncludingThis is inherited from nsIDocument.
// WebIDL API
- bool Load(const nsAString& aUrl, mozilla::ErrorResult& aRv);
+ bool Load(const nsAString& aUrl, CallerType aCallerType, ErrorResult& aRv);
bool Async() const
{
return mAsync;
}
void SetAsync(bool aAsync)
{
mAsync = aAsync;
}
--- a/dom/xslt/nsIXSLTProcessorPrivate.idl
+++ b/dom/xslt/nsIXSLTProcessorPrivate.idl
@@ -9,15 +9,9 @@
interface nsIXSLTProcessorPrivate : nsISupports
{
/**
* Disables all loading of external documents, such as from
* <xsl:import> and document()
* Defaults to off and is *not* reset by calls to reset()
*/
const unsigned long DISABLE_ALL_LOADS = 1;
-
- /**
- * Flags for this processor. Defaults to 0. See individual flags above
- * for documentation for effect of reset()
- */
- attribute unsigned long flags;
};
--- a/dom/xslt/xslt/txMozillaXSLTProcessor.cpp
+++ b/dom/xslt/xslt/txMozillaXSLTProcessor.cpp
@@ -1006,36 +1006,26 @@ txMozillaXSLTProcessor::Reset()
mStylesheetDocument = nullptr;
mEmbeddedStylesheetRoot = nullptr;
mCompileResult = NS_OK;
mVariables.clear();
return NS_OK;
}
-NS_IMETHODIMP
-txMozillaXSLTProcessor::SetFlags(uint32_t aFlags)
+void
+txMozillaXSLTProcessor::SetFlags(uint32_t aFlags, SystemCallerGuarantee)
{
- NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(),
- NS_ERROR_DOM_SECURITY_ERR);
-
mFlags = aFlags;
-
- return NS_OK;
}
-NS_IMETHODIMP
-txMozillaXSLTProcessor::GetFlags(uint32_t* aFlags)
+uint32_t
+txMozillaXSLTProcessor::Flags(SystemCallerGuarantee)
{
- NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(),
- NS_ERROR_DOM_SECURITY_ERR);
-
- *aFlags = mFlags;
-
- return NS_OK;
+ return mFlags;
}
NS_IMETHODIMP
txMozillaXSLTProcessor::LoadStyleSheet(nsIURI* aUri,
nsIDocument* aLoaderDocument)
{
mozilla::net::ReferrerPolicy refpol = mozilla::net::RP_Unset;
if (mStylesheetDocument) {
--- a/dom/xslt/xslt/txMozillaXSLTProcessor.h
+++ b/dom/xslt/xslt/txMozillaXSLTProcessor.h
@@ -12,16 +12,17 @@
#include "nsIXSLTProcessor.h"
#include "nsIXSLTProcessorPrivate.h"
#include "txExpandedNameMap.h"
#include "txNamespaceMap.h"
#include "nsCycleCollectionParticipant.h"
#include "nsWrapperCache.h"
#include "mozilla/Attributes.h"
#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/net/ReferrerPolicy.h"
class nsINode;
class nsIDOMNode;
class nsIURI;
class txStylesheet;
class txResultRecycler;
class txIGlobalParameter;
@@ -123,22 +124,18 @@ public:
mozilla::ErrorResult& aRv);
void RemoveParameter(const nsAString& aNamespaceURI,
const nsAString& aLocalName,
mozilla::ErrorResult& aRv)
{
aRv = RemoveParameter(aNamespaceURI, aLocalName);
}
- uint32_t Flags()
- {
- uint32_t flags;
- GetFlags(&flags);
- return flags;
- }
+ uint32_t Flags(mozilla::dom::SystemCallerGuarantee);
+ void SetFlags(uint32_t aFlags, mozilla::dom::SystemCallerGuarantee);
nsresult setStylesheet(txStylesheet* aStylesheet);
void reportError(nsresult aResult, const char16_t *aErrorText,
const char16_t *aSourceText);
nsIDOMNode *GetSourceContentModel()
{
return mSource;
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -119,55 +119,65 @@ ImageContainerListener::NotifyComposite(
void
ImageContainerListener::ClearImageContainer()
{
MutexAutoLock lock(mLock);
mImageContainer = nullptr;
}
void
-ImageContainer::EnsureImageClient(bool aCreate)
+ImageContainer::EnsureImageClient()
{
// If we're not forcing a new ImageClient, then we can skip this if we don't have an existing
// ImageClient, or if the existing one belongs to an IPC actor that is still open.
- if (!aCreate && (!mImageClient || mImageClient->GetForwarder()->GetLayersIPCActor()->IPCOpen())) {
+ if (!mIsAsync) {
+ return;
+ }
+ if (mImageClient && mImageClient->GetForwarder()->GetLayersIPCActor()->IPCOpen()) {
return;
}
RefPtr<ImageBridgeChild> imageBridge = ImageBridgeChild::GetSingleton();
if (imageBridge) {
mImageClient = imageBridge->CreateImageClient(CompositableType::IMAGE, this);
if (mImageClient) {
mAsyncContainerHandle = mImageClient->GetAsyncHandle();
mNotifyCompositeListener = new ImageContainerListener(this);
+ } else {
+ // It's okay to drop the async container handle since the ImageBridgeChild
+ // is going to die anyway.
+ mAsyncContainerHandle = CompositableHandle();
+ mNotifyCompositeListener = nullptr;
}
}
}
ImageContainer::ImageContainer(Mode flag)
: mReentrantMonitor("ImageContainer.mReentrantMonitor"),
mGenerationCounter(++sGenerationCounter),
mPaintCount(0),
mDroppedImageCount(0),
mImageFactory(new ImageFactory()),
mRecycleBin(new BufferRecycleBin()),
+ mIsAsync(flag == ASYNCHRONOUS),
mCurrentProducerID(-1)
{
if (flag == ASYNCHRONOUS) {
- EnsureImageClient(true);
+ EnsureImageClient();
}
}
ImageContainer::ImageContainer(const CompositableHandle& aHandle)
: mReentrantMonitor("ImageContainer.mReentrantMonitor"),
mGenerationCounter(++sGenerationCounter),
mPaintCount(0),
mDroppedImageCount(0),
mImageFactory(nullptr),
mRecycleBin(nullptr),
+ mIsAsync(true),
mAsyncContainerHandle(aHandle),
mCurrentProducerID(-1)
{
MOZ_ASSERT(mAsyncContainerHandle);
}
ImageContainer::~ImageContainer()
{
@@ -180,28 +190,28 @@ ImageContainer::~ImageContainer()
}
}
}
RefPtr<PlanarYCbCrImage>
ImageContainer::CreatePlanarYCbCrImage()
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
- EnsureImageClient(false);
+ EnsureImageClient();
if (mImageClient && mImageClient->AsImageClientSingle()) {
return new SharedPlanarYCbCrImage(mImageClient);
}
return mImageFactory->CreatePlanarYCbCrImage(mScaleHint, mRecycleBin);
}
RefPtr<SharedRGBImage>
ImageContainer::CreateSharedRGBImage()
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
- EnsureImageClient(false);
+ EnsureImageClient();
if (!mImageClient || !mImageClient->AsImageClientSingle()) {
return nullptr;
}
return new SharedRGBImage(mImageClient);
}
void
ImageContainer::SetCurrentImageInternal(const nsTArray<NonOwningImage>& aImages)
@@ -337,23 +347,24 @@ ImageContainer::SetCurrentImagesInTransa
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ASSERTION(!mImageClient, "Should use async image transfer with ImageBridge.");
SetCurrentImageInternal(aImages);
}
bool ImageContainer::IsAsync() const
{
- return !!mAsyncContainerHandle;
+ return mIsAsync;
}
CompositableHandle ImageContainer::GetAsyncContainerHandle()
{
- NS_ASSERTION(IsAsync(),"Shared image ID is only relevant to async ImageContainers");
- EnsureImageClient(false);
+ NS_ASSERTION(IsAsync(), "Shared image ID is only relevant to async ImageContainers");
+ NS_ASSERTION(mAsyncContainerHandle, "Should have a shared image ID");
+ EnsureImageClient();
return mAsyncContainerHandle;
}
bool
ImageContainer::HasCurrentImage()
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -604,17 +604,17 @@ private:
void SetCurrentImageInternal(const nsTArray<NonOwningImage>& aImages);
// This is called to ensure we have an active image, this may not be true
// when we're storing image information in a RemoteImageData structure.
// NOTE: If we have remote data mRemoteDataMutex should be locked when
// calling this function!
void EnsureActiveImage();
- void EnsureImageClient(bool aCreate);
+ void EnsureImageClient();
// ReentrantMonitor to protect thread safe access to the "current
// image", and any other state which is shared between threads.
ReentrantMonitor mReentrantMonitor;
nsTArray<OwningImage> mCurrentImages;
// Updates every time mActiveImage changes
@@ -644,16 +644,17 @@ private:
// sucessfully created with ENABLE_ASYNC, or points to null otherwise.
// 'unsuccessful' in this case only means that the ImageClient could not
// be created, most likely because off-main-thread compositing is not enabled.
// In this case the ImageContainer is perfectly usable, but it will forward
// frames to the compositor through transactions in the main thread rather than
// asynchronusly using the ImageBridge IPDL protocol.
RefPtr<ImageClient> mImageClient;
+ bool mIsAsync;
CompositableHandle mAsyncContainerHandle;
nsTArray<FrameID> mFrameIDsNotYetComposited;
// ProducerID for last current image(s), including the frames in
// mFrameIDsNotYetComposited
ProducerID mCurrentProducerID;
RefPtr<ImageContainerListener> mNotifyCompositeListener;
--- a/gfx/layers/apz/util/CheckerboardReportService.cpp
+++ b/gfx/layers/apz/util/CheckerboardReportService.cpp
@@ -154,17 +154,17 @@ NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(C
/*static*/ bool
CheckerboardReportService::IsEnabled(JSContext* aCtx, JSObject* aGlobal)
{
// Only allow this in the parent process
if (!XRE_IsParentProcess()) {
return false;
}
// Allow privileged code or about:checkerboard (unprivileged) to access this.
- return nsContentUtils::IsCallerChrome()
+ return nsContentUtils::IsSystemCaller(aCtx)
|| nsContentUtils::IsSpecificAboutPage(aGlobal, "about:checkerboard");
}
/*static*/ already_AddRefed<CheckerboardReportService>
CheckerboardReportService::Constructor(const dom::GlobalObject& aGlobal, ErrorResult& aRv)
{
RefPtr<CheckerboardReportService> ces = new CheckerboardReportService(aGlobal.GetAsSupports());
return ces.forget();
--- a/gfx/layers/client/ImageClient.cpp
+++ b/gfx/layers/client/ImageClient.cpp
@@ -220,20 +220,30 @@ ImageClientSingle::UpdateImage(ImageCont
}
if (!texture) {
// Slow path, we should not be hitting it very often and if we do it means
// we are using an Image class that is not backed by textureClient and we
// should fix it.
texture = CreateTextureClientForImage(image, GetForwarder());
}
- if (!texture || !AddTextureClient(texture)) {
+
+ if (!texture) {
return false;
}
+ // We check if the texture's allocator is still open, since in between media
+ // decoding a frame and adding it to the compositable, we could have
+ // restarted the GPU process.
+ if (!texture->GetAllocator()->IPCOpen()) {
+ continue;
+ }
+ if (!AddTextureClient(texture)) {
+ return false;
+ }
CompositableForwarder::TimedTextureClient* t = textures.AppendElement();
t->mTextureClient = texture;
t->mTimeStamp = img.mTimeStamp;
t->mPictureRect = image->GetPictureRect();
t->mFrameID = img.mFrameID;
t->mProducerID = img.mProducerID;
@@ -285,15 +295,21 @@ bool
ImageClientBridge::UpdateImage(ImageContainer* aContainer, uint32_t aContentFlags)
{
if (!GetForwarder() || !mLayer) {
return false;
}
if (mAsyncContainerHandle == aContainer->GetAsyncContainerHandle()) {
return true;
}
+
mAsyncContainerHandle = aContainer->GetAsyncContainerHandle();
+ if (!mAsyncContainerHandle) {
+ // If we couldn't contact a working ImageBridgeParent, just return.
+ return true;
+ }
+
static_cast<ShadowLayerForwarder*>(GetForwarder())->AttachAsyncCompositable(mAsyncContainerHandle, mLayer);
return true;
}
} // namespace layers
} // namespace mozilla
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -415,18 +415,22 @@ LayerTransactionParent::RecvUpdate(const
return IPC_OK();
}
ImageBridgeParent* imageBridge = ImageBridgeParent::GetInstance(OtherPid());
if (!imageBridge) {
return IPC_FAIL_NO_REASON(this);
}
RefPtr<CompositableHost> host = imageBridge->FindCompositable(op.compositable());
if (!host) {
- NS_ERROR("CompositableHost not found in the map");
- return IPC_FAIL_NO_REASON(this);
+ // This normally should not happen, but can after a GPU process crash.
+ // Media may not have had time to update the ImageContainer associated
+ // with a video frame, and we may try to attach a stale CompositableHandle.
+ // Rather than break the whole transaction, we just continue.
+ gfxCriticalNote << "CompositableHost " << op.compositable().Value() << " not found";
+ continue;
}
if (!Attach(AsLayer(op.layer()), host, true)) {
return IPC_FAIL_NO_REASON(this);
}
if (mLayerManager->GetCompositor()) {
host->SetCompositorID(mLayerManager->GetCompositor()->GetCompositorID());
}
break;
--- a/gfx/thebes/gfxPlatformGtk.cpp
+++ b/gfx/thebes/gfxPlatformGtk.cpp
@@ -404,17 +404,17 @@ double
gfxPlatformGtk::GetDPIScale()
{
// Integer scale factors work well with GTK window scaling, image scaling,
// and pixel alignment, but there is a range where 1 is too small and 2 is
// too big. An additional step of 1.5 is added because this is common
// scale on WINNT and at this ratio the advantages of larger rendering
// outweigh the disadvantages from scaling and pixel mis-alignment.
int32_t dpi = GetDPI();
- if (dpi < 144) {
+ if (dpi < 132) {
return 1.0;
} else if (dpi < 168) {
return 1.5;
} else {
return round(dpi/96.0);
}
}
--- a/ipc/chromium/src/base/compiler_specific.h
+++ b/ipc/chromium/src/base/compiler_specific.h
@@ -62,18 +62,20 @@
#define MSVC_PUSH_WARNING_LEVEL(n)
#define MSVC_POP_WARNING()
#define MSVC_DISABLE_OPTIMIZE()
#define MSVC_ENABLE_OPTIMIZE()
#define ALLOW_THIS_IN_INITIALIZER_LIST(code) code
#endif // COMPILER_MSVC
-
-#if defined(COMPILER_GCC)
-#define ALLOW_UNUSED __attribute__((unused))
+// Annotate a function indicating the caller must examine the return value.
+// Use like:
+// int foo() WARN_UNUSED_RESULT;
+// To explicitly ignore a result, see |ignore_result()| in base/macros.h.
+#undef WARN_UNUSED_RESULT
+#if defined(COMPILER_GCC) || defined(__clang__)
#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
-#else // Not GCC
-#define ALLOW_UNUSED
+#else
#define WARN_UNUSED_RESULT
#endif
#endif // BASE_COMPILER_SPECIFIC_H_
--- a/js/ipc/WrapperOwner.cpp
+++ b/js/ipc/WrapperOwner.cpp
@@ -426,17 +426,17 @@ WrapperOwner::DOMQI(JSContext* cx, JS::H
{
// Someone's calling us, handle nsISupports specially to avoid unnecessary
// CPOW traffic.
HandleValue id = args[0];
if (id.isObject()) {
RootedObject idobj(cx, &id.toObject());
nsCOMPtr<nsIJSID> jsid;
- nsresult rv = UnwrapArg<nsIJSID>(idobj, getter_AddRefs(jsid));
+ nsresult rv = UnwrapArg<nsIJSID>(cx, idobj, getter_AddRefs(jsid));
if (NS_SUCCEEDED(rv)) {
MOZ_ASSERT(jsid, "bad wrapJS");
const nsID* idptr = jsid->GetID();
if (idptr->Equals(NS_GET_IID(nsISupports))) {
args.rval().set(args.thisv());
return true;
}
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2594,27 +2594,27 @@ JS_GetElement(JSContext* cx, HandleObjec
}
JS_PUBLIC_API(bool)
JS_ForwardSetPropertyTo(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
HandleValue receiver, ObjectOpResult& result)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
- assertSameCompartment(cx, obj, id, receiver);
+ assertSameCompartment(cx, obj, id, v, receiver);
return SetProperty(cx, obj, id, v, receiver, result);
}
JS_PUBLIC_API(bool)
JS_SetPropertyById(JSContext* cx, HandleObject obj, HandleId id, HandleValue v)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
- assertSameCompartment(cx, obj, id);
+ assertSameCompartment(cx, obj, id, v);
RootedValue receiver(cx, ObjectValue(*obj));
ObjectOpResult ignored;
return SetProperty(cx, obj, id, v, receiver, ignored);
}
JS_PUBLIC_API(bool)
JS_SetProperty(JSContext* cx, HandleObject obj, const char* name, HandleValue v)
@@ -2758,17 +2758,17 @@ JS_DeleteElement(JSContext* cx, HandleOb
return JS_DeleteElement(cx, obj, index, ignored);
}
JS_PUBLIC_API(bool)
JS_Enumerate(JSContext* cx, HandleObject obj, JS::MutableHandle<IdVector> props)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
- assertSameCompartment(cx, obj);
+ assertSameCompartment(cx, obj, props);
MOZ_ASSERT(props.empty());
AutoIdVector ids(cx);
if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, &ids))
return false;
return props.append(ids.begin(), ids.end());
}
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -78,16 +78,21 @@ class CompartmentChecker
check(rooted.get());
}
template<typename T>
void check(Handle<T> handle) {
check(handle.get());
}
+ template<typename T>
+ void check(MutableHandle<T> handle) {
+ check(handle.get());
+ }
+
void checkAtom(gc::Cell* cell) {
#ifdef DEBUG
// Atoms which move across zone boundaries need to be marked in the new
// zone, see JS_MarkCrossZoneId.
if (compartment) {
JSRuntime* rt = compartment->runtimeFromAnyThread();
MOZ_ASSERT(rt->gc.atomMarking.atomIsMarked(compartment->zone(), cell));
}
@@ -111,16 +116,30 @@ class CompartmentChecker
if (v.isObject())
check(&v.toObject());
else if (v.isString())
check(v.toString());
else if (v.isSymbol())
check(v.toSymbol());
}
+ // Check the contents of any container class that supports the C++
+ // iteration protocol, eg GCVector<jsid>.
+ template <typename Container>
+ typename mozilla::EnableIf<
+ mozilla::IsSame<
+ decltype(((Container*)nullptr)->begin()),
+ decltype(((Container*)nullptr)->end())
+ >::value
+ >::Type
+ check(const Container& container) {
+ for (auto i : container)
+ check(i);
+ }
+
void check(const ValueArray& arr) {
for (size_t i = 0; i < arr.length; i++)
check(arr.array[i]);
}
void check(const JSValueArray& arr) {
for (size_t i = 0; i < arr.length; i++)
check(arr.array[i]);
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -284,19 +284,22 @@ SandboxFetch(JSContext* cx, JS::HandleOb
if (!options.Init(cx, args.hasDefined(1) ? args[1] : JS::NullHandleValue,
"Argument 2 of fetch", false)) {
return false;
}
nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(scope);
if (!global) {
return false;
}
+ dom::CallerType callerType = nsContentUtils::IsSystemCaller(cx) ?
+ dom::CallerType::System : dom::CallerType::NonSystem;
ErrorResult rv;
RefPtr<dom::Promise> response =
- FetchRequest(global, Constify(request), Constify(options), rv);
+ FetchRequest(global, Constify(request), Constify(options),
+ callerType, rv);
if (rv.MaybeSetPendingException(cx)) {
return false;
}
args.rval().setObject(*response->PromiseObj());
return true;
}
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -1269,17 +1269,17 @@ XPCJSContext::InterruptCallback(JSContex
// has finished bootstrapping. Avoid crashing in nsContentUtils below.
if (!nsContentUtils::IsInitialized())
return true;
// This is at least the second interrupt callback we've received since
// returning to the event loop. See how long it's been, and what the limit
// is.
TimeDuration duration = TimeStamp::NowLoRes() - self->mSlowScriptCheckpoint;
- bool chrome = nsContentUtils::IsCallerChrome();
+ bool chrome = nsContentUtils::IsSystemCaller(cx);
const char* prefName = chrome ? PREF_MAX_SCRIPT_RUN_TIME_CHROME
: PREF_MAX_SCRIPT_RUN_TIME_CONTENT;
int32_t limit = Preferences::GetInt(prefName, chrome ? 20 : 10);
// If there's no limit, or we're within the limit, let it go.
if (limit == 0 || duration.ToSeconds() < limit / 2.0)
return true;
@@ -3220,17 +3220,17 @@ ReadSourceFromFilename(JSContext* cx, co
// The JS engine calls this object's 'load' member function when it needs
// the source for a chrome JS function. See the comment in the XPCJSContext
// constructor.
class XPCJSSourceHook: public js::SourceHook {
bool load(JSContext* cx, const char* filename, char16_t** src, size_t* length) {
*src = nullptr;
*length = 0;
- if (!nsContentUtils::IsCallerChrome())
+ if (!nsContentUtils::IsSystemCaller(cx))
return true;
if (!filename)
return true;
nsresult rv = ReadSourceFromFilename(cx, filename, src, length);
if (NS_FAILED(rv)) {
xpc::Throw(cx, rv);
--- a/js/xpconnect/src/XPCString.cpp
+++ b/js/xpconnect/src/XPCString.cpp
@@ -43,16 +43,17 @@ XPCStringConvert::ClearZoneCache(JS::Zon
{
// Although we clear the cache in FinalizeDOMString if needed, we also clear
// the cache here to avoid a dangling JSString* pointer when compacting GC
// moves the external string in memory.
ZoneStringCache* cache = static_cast<ZoneStringCache*>(JS_GetZoneUserData(zone));
if (cache) {
cache->mBuffer = nullptr;
+ cache->mLength = 0;
cache->mString = nullptr;
}
}
// static
void
XPCStringConvert::FinalizeLiteral(JS::Zone* zone, const JSStringFinalizer* fin, char16_t* chars)
{
@@ -67,16 +68,17 @@ XPCStringConvert::FinalizeDOMString(JS::
{
nsStringBuffer* buf = nsStringBuffer::FromData(chars);
// Clear the ZoneStringCache if needed, as this can be called outside GC
// when flattening an external string.
ZoneStringCache* cache = static_cast<ZoneStringCache*>(JS_GetZoneUserData(zone));
if (cache && cache->mBuffer == buf) {
cache->mBuffer = nullptr;
+ cache->mLength = 0;
cache->mString = nullptr;
}
buf->Release();
}
const JSStringFinalizer XPCStringConvert::sDOMStringFinalizer =
{ XPCStringConvert::FinalizeDOMString };
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -195,17 +195,17 @@ XPC_WN_DoubleWrappedGetter(JSContext* cx
// responded to this get property call and now gives no object.
// XXX Should this throw something at the caller?
args.rval().setNull();
return true;
}
// It is a double wrapped object. This should really never appear in
// content these days, but addons still do it - see bug 965921.
- if (MOZ_UNLIKELY(!nsContentUtils::IsCallerChrome())) {
+ if (MOZ_UNLIKELY(!nsContentUtils::IsSystemCaller(cx))) {
JS_ReportErrorASCII(cx, "Attempt to use .wrappedJSObject in untrusted code");
return false;
}
args.rval().setObject(*realObject);
return JS_WrapValue(cx, args.rval());
}
/***************************************************************************/
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -1129,17 +1129,17 @@ ReadScriptOrFunction(nsIObjectInputStrea
uint8_t flags;
nsresult rv = stream->Read8(&flags);
if (NS_FAILED(rv))
return rv;
// We don't serialize mutedError-ness of scripts, which is fine as long as
// we only serialize system and XUL-y things. We can detect this by checking
// where the caller wants us to deserialize.
- MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome() ||
+ MOZ_RELEASE_ASSERT(nsContentUtils::IsSystemCaller(cx) ||
CurrentGlobalOrNull(cx) == xpc::CompilationScope());
uint32_t size;
rv = stream->Read32(&size);
if (NS_FAILED(rv))
return rv;
char* data;
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -224,21 +224,28 @@ class XPCStringConvert
// One-slot cache, because it turns out it's common for web pages to
// get the same string a few times in a row. We get about a 40% cache
// hit rate on this cache last it was measured. We'd get about 70%
// hit rate with a hashtable with removal on finalization, but that
// would take a lot more machinery.
struct ZoneStringCache
{
// mString owns mBuffer. mString is a JS thing, so it can only die
- // during GC. We clear mString and mBuffer during GC. As long as
- // the above holds, mBuffer should not be a dangling pointer, so
+ // during GC, though it can drop its ref to the buffer if it gets
+ // flattened and wasn't null-terminated. We clear mString and mBuffer
+ // during GC and in our finalizer (to catch the flatterning case). As
+ // long as the above holds, mBuffer should not be a dangling pointer, so
// using this as a cache key should be safe.
- void* mBuffer;
- JSString* mString;
+ //
+ // We also need to include the string's length in the cache key, because
+ // now that we allow non-null-terminated buffers we can have two strings
+ // with the same mBuffer but different lengths.
+ void* mBuffer = nullptr;
+ uint32_t mLength = 0;
+ JSString* mString = nullptr;
};
public:
// If the string shares the readable's buffer, that buffer will
// get assigned to *sharedBuffer. Otherwise null will be
// assigned.
static bool ReadableToJSVal(JSContext* cx, const nsAString& readable,
@@ -247,17 +254,17 @@ public:
// Convert the given stringbuffer/length pair to a jsval
static MOZ_ALWAYS_INLINE bool
StringBufferToJSVal(JSContext* cx, nsStringBuffer* buf, uint32_t length,
JS::MutableHandleValue rval, bool* sharedBuffer)
{
JS::Zone* zone = js::GetContextZone(cx);
ZoneStringCache* cache = static_cast<ZoneStringCache*>(JS_GetZoneUserData(zone));
- if (cache && buf == cache->mBuffer) {
+ if (cache && buf == cache->mBuffer && length == cache->mLength) {
MOZ_ASSERT(JS::GetStringZone(cache->mString) == zone);
JS::MarkStringAsLive(zone, cache->mString);
rval.setString(cache->mString);
*sharedBuffer = false;
return true;