This commit is contained in:
Ryan VanderMeulen 2017-11-10 16:14:16 -05:00
Родитель ea86e1d617 b16410f51c
Коммит ef843a0e30
210 изменённых файлов: 6339 добавлений и 4286 удалений

Просмотреть файл

@ -53,11 +53,6 @@ GARBAGE += $(addprefix $(FINAL_TARGET)/defaults/pref/, firefox.js)
endif
# channel-prefs.js is handled separate from other prefs due to bug 756325
libs:: $(srcdir)/profile/channel-prefs.js
$(NSINSTALL) -D $(DIST)/bin/defaults/pref
$(call py_action,preprocessor,-Fsubstitution $(PREF_PPFLAGS) $(ACDEFINES) $^ -o $(DIST)/bin/defaults/pref/channel-prefs.js)
ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
MAC_APP_NAME = $(MOZ_APP_DISPLAYNAME)

Просмотреть файл

@ -36,18 +36,15 @@ DIRS += ['profile/extensions']
GeckoProgram(CONFIG['MOZ_APP_NAME'])
JS_PREFERENCE_PP_FILES += [
'profile/firefox.js',
]
SOURCES += [
'nsBrowserApp.cpp',
]
FINAL_TARGET_FILES += ['blocklist.xml']
FINAL_TARGET_FILES.defaults += ['permissions']
# Neither channel-prefs.js nor firefox.exe want to end up in dist/bin/browser.
DIST_SUBDIR = ""
DEFINES['APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
# channel-prefs.js is handled separate from other prefs due to bug 756325
JS_PREFERENCE_PP_FILES += ['profile/channel-prefs.js']
LOCAL_INCLUDES += [
'!/build',
@ -74,9 +71,6 @@ if CONFIG['OS_ARCH'] == 'WINNT':
RCINCLUDE = 'splash.rc'
DEFINES['MOZ_PHOENIX'] = True
for cdm in CONFIG['MOZ_EME_MODULES']:
DEFINES['MOZ_%s_EME' % cdm.upper()] = True
if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_ARCH'] == 'WINNT':
# For sandbox includes and the include dependencies those have
LOCAL_INCLUDES += [
@ -112,9 +106,6 @@ if CONFIG['MOZ_LINKER']:
if CONFIG['HAVE_CLOCK_MONOTONIC']:
OS_LIBS += CONFIG['REALTIME_LIBS']
if CONFIG['MOZ_GPSD']:
DEFINES['MOZ_GPSD'] = True
if CONFIG['MOZ_LINUX_32_SSE2_STARTUP_ERROR']:
DEFINES['MOZ_LINUX_32_SSE2_STARTUP_ERROR'] = True
COMPILE_FLAGS['OS_CXXFLAGS'] = [

Просмотреть файл

@ -2,4 +2,5 @@
* 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/. */
#filter substitution
pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@");

Просмотреть файл

@ -48,24 +48,6 @@ var BrowserPageActions = {
*/
init() {
this.placeAllActions();
// Add a click listener to #page-action-buttons for blocking clicks on
// disabled actions in the urlbar. Normally we'd do this by setting
// `pointer-events: none` in the CSS, but that also blocks context menu
// events, and we want the context menu even on disabled actions so that
// they can be removed from the urlbar.
this.mainButtonNode.parentNode.addEventListener("click", event => {
if (event.button == 2) {
// Let context-clicks be handled normally.
return;
}
let node = event.originalTarget;
let action = this.actionForNode(node);
if (action && action.getDisabled(window)) {
event.preventDefault();
event.stopPropagation();
}
}, true);
},
/**
@ -86,7 +68,7 @@ var BrowserPageActions = {
// node.insertBefore() is always passed null, and nodes are always appended.
// That will break the position of nodes that should be inserted before
// nodes that are in markup, which in turn can break other nodes.
let actionsInUrlbar = PageActions.actionsInUrlbar;
let actionsInUrlbar = PageActions.actionsInUrlbar(window);
for (let i = actionsInUrlbar.length - 1; i >= 0; i--) {
let action = actionsInUrlbar[i];
this.placeActionInUrlbar(action);
@ -112,21 +94,19 @@ var BrowserPageActions = {
* The action to place.
*/
placeActionInPanel(action) {
let insertBeforeID = PageActions.nextActionIDInPanel(action);
let id = this.panelButtonNodeIDForActionID(action.id);
let node = document.getElementById(id);
if (!node) {
let panelViewNode;
[node, panelViewNode] = this._makePanelButtonNodeForAction(action);
node.id = id;
let insertBeforeNode = null;
if (insertBeforeID) {
let insertBeforeNodeID =
this.panelButtonNodeIDForActionID(insertBeforeID);
insertBeforeNode = document.getElementById(insertBeforeNodeID);
}
let insertBeforeID = PageActions.nextActionIDInPanel(action);
let insertBeforeNode =
insertBeforeID ? this.panelButtonNodeForActionID(insertBeforeID) :
null;
this.mainViewBodyNode.insertBefore(node, insertBeforeNode);
this.updateAction(action);
this._updateActionDisabledInPanel(action);
action.onPlacedInPanel(node);
if (panelViewNode) {
action.subview.onPlaced(panelViewNode);
@ -356,11 +336,10 @@ var BrowserPageActions = {
* The action to place.
*/
placeActionInUrlbar(action) {
let insertBeforeID = PageActions.nextActionIDInUrlbar(action);
let id = this.urlbarButtonNodeIDForActionID(action.id);
let node = document.getElementById(id);
if (!action.shownInUrlbar) {
if (!action.shouldShowInUrlbar(window)) {
if (node) {
if (action.__urlbarNodeInMarkup) {
node.hidden = true;
@ -382,14 +361,11 @@ var BrowserPageActions = {
}
if (newlyPlaced) {
let parentNode = this.mainButtonNode.parentNode;
let insertBeforeNode = null;
if (insertBeforeID) {
let insertBeforeNodeID =
this.urlbarButtonNodeIDForActionID(insertBeforeID);
insertBeforeNode = document.getElementById(insertBeforeNodeID);
}
parentNode.insertBefore(node, insertBeforeNode);
let insertBeforeID = PageActions.nextActionIDInUrlbar(window, action);
let insertBeforeNode =
insertBeforeID ? this.urlbarButtonNodeForActionID(insertBeforeID) :
null;
this.mainButtonNode.parentNode.insertBefore(node, insertBeforeNode);
this.updateAction(action);
action.onPlacedInUrlbar(node);
@ -398,8 +374,7 @@ var BrowserPageActions = {
// button. Why not set tooltiptext to action.title when the node is
// created? Because the consumer may set a title dynamically.
if (!node.hasAttribute("tooltiptext")) {
let panelNodeID = this.panelButtonNodeIDForActionID(action.id);
let panelNode = document.getElementById(panelNodeID);
let panelNode = this.panelButtonNodeForActionID(action.id);
if (panelNode) {
node.setAttribute("tooltiptext", panelNode.getAttribute("label"));
}
@ -412,9 +387,6 @@ var BrowserPageActions = {
buttonNode.classList.add("urlbar-icon", "urlbar-page-action");
buttonNode.setAttribute("actionid", action.id);
buttonNode.setAttribute("role", "button");
buttonNode.addEventListener("contextmenu", event => {
BrowserPageActions.onContextMenu(event);
});
if (action.nodeAttributes) {
for (let name in action.nodeAttributes) {
buttonNode.setAttribute(name, action.nodeAttributes[name]);
@ -439,8 +411,7 @@ var BrowserPageActions = {
},
_removeActionFromPanel(action) {
let id = this.panelButtonNodeIDForActionID(action.id);
let node = document.getElementById(id);
let node = this.panelButtonNodeForActionID(action.id);
if (node) {
node.remove();
}
@ -466,8 +437,7 @@ var BrowserPageActions = {
},
_removeActionFromUrlbar(action) {
let id = this.urlbarButtonNodeIDForActionID(action.id);
let node = document.getElementById(id);
let node = this.urlbarButtonNodeForActionID(action.id);
if (node) {
node.remove();
}
@ -479,56 +449,51 @@ var BrowserPageActions = {
*
* @param action (PageActions.Action, required)
* The action to update.
* @param nameToUpdate (string, optional)
* The property's name. If not given, then DOM nodes will be updated
* to reflect the current values of all properties.
* @param propertyName (string, optional)
* The name of the property to update. If not given, then DOM nodes
* will be updated to reflect the current values of all properties.
*/
updateAction(action, nameToUpdate = null) {
let names = nameToUpdate ? [nameToUpdate] : [
"disabled",
updateAction(action, propertyName = null) {
let propertyNames = propertyName ? [propertyName] : [
"iconURL",
"title",
"tooltip",
];
for (let name of names) {
for (let name of propertyNames) {
let upper = name[0].toUpperCase() + name.substr(1);
this[`_updateAction${upper}`](action);
}
},
_updateActionDisabled(action) {
let nodeIDs = [
this.panelButtonNodeIDForActionID(action.id),
this.urlbarButtonNodeIDForActionID(action.id),
];
for (let nodeID of nodeIDs) {
let node = document.getElementById(nodeID);
if (node) {
if (action.getDisabled(window)) {
node.setAttribute("disabled", "true");
} else {
node.removeAttribute("disabled");
}
this._updateActionDisabledInPanel(action);
this.placeActionInUrlbar(action);
},
_updateActionDisabledInPanel(action) {
let panelButton = this.panelButtonNodeForActionID(action.id);
if (panelButton) {
if (action.getDisabled(window)) {
panelButton.setAttribute("disabled", "true");
} else {
panelButton.removeAttribute("disabled");
}
}
},
_updateActionIconURL(action) {
let nodeIDs = [
this.panelButtonNodeIDForActionID(action.id),
this.urlbarButtonNodeIDForActionID(action.id),
];
for (let nodeID of nodeIDs) {
let node = document.getElementById(nodeID);
if (node) {
for (let size of [16, 32]) {
let url = action.iconURLForSize(size, window);
let prop = `--pageAction-image-${size}px`;
if (url) {
node.style.setProperty(prop, `url("${url}")`);
} else {
node.style.removeProperty(prop);
}
let nodes = [
this.panelButtonNodeForActionID(action.id),
this.urlbarButtonNodeForActionID(action.id),
].filter(n => !!n);
for (let node of nodes) {
for (let size of [16, 32]) {
let url = action.iconURLForSize(size, window);
let prop = `--pageAction-image-${size}px`;
if (url) {
node.style.setProperty(prop, `url("${url}")`);
} else {
node.style.removeProperty(prop);
}
}
}
@ -543,13 +508,12 @@ var BrowserPageActions = {
// return is to ignore that empty title.
return;
}
let attrNamesByNodeIDFnName = {
panelButtonNodeIDForActionID: "label",
urlbarButtonNodeIDForActionID: "aria-label",
let attrNamesByNodeFnName = {
panelButtonNodeForActionID: "label",
urlbarButtonNodeForActionID: "aria-label",
};
for (let [fnName, attrName] of Object.entries(attrNamesByNodeIDFnName)) {
let nodeID = this[fnName](action.id);
let node = document.getElementById(nodeID);
for (let [fnName, attrName] of Object.entries(attrNamesByNodeFnName)) {
let node = this[fnName](action.id);
if (node) {
node.setAttribute(attrName, title);
}
@ -559,9 +523,7 @@ var BrowserPageActions = {
},
_updateActionTooltip(action) {
let node = document.getElementById(
this.urlbarButtonNodeIDForActionID(action.id)
);
let node = this.urlbarButtonNodeForActionID(action.id);
if (node) {
let tooltip = action.getTooltip(window) || action.getTitle(window);
node.setAttribute("tooltiptext", tooltip);
@ -575,8 +537,10 @@ var BrowserPageActions = {
PageActions.logTelemetry("used", action, buttonNode);
// If we're in the panel, open a subview inside the panel:
// Note that we can't use this.panelNode.contains(buttonNode) here
// because of XBL boundaries breaking ELement.contains.
if (action.subview && buttonNode && buttonNode.closest("panel") == this.panelNode) {
// because of XBL boundaries breaking Element.contains.
if (action.subview &&
buttonNode &&
buttonNode.closest("panel") == this.panelNode) {
let panelViewNodeID = this._panelViewNodeIDForActionID(action.id, false);
let panelViewNode = document.getElementById(panelViewNodeID);
action.subview.onShowing(panelViewNode);
@ -627,6 +591,17 @@ var BrowserPageActions = {
return action;
},
/**
* The given action's top-level button in the main panel.
*
* @param actionID (string, required)
* The action ID.
* @return (DOM node) The action's button in the main panel.
*/
panelButtonNodeForActionID(actionID) {
return document.getElementById(this.panelButtonNodeIDForActionID(actionID));
},
/**
* The ID of the given action's top-level button in the main panel.
*
@ -638,6 +613,17 @@ var BrowserPageActions = {
return `pageAction-panel-${actionID}`;
},
/**
* The given action's button in the urlbar.
*
* @param actionID (string, required)
* The action ID.
* @return (DOM node) The action's urlbar button node.
*/
urlbarButtonNodeForActionID(actionID) {
return document.getElementById(this.urlbarButtonNodeIDForActionID(actionID));
},
/**
* The ID of the given action's button in the urlbar.
*
@ -722,8 +708,7 @@ var BrowserPageActions = {
*/
showPanel(event = null) {
for (let action of PageActions.actions) {
let buttonNodeID = this.panelButtonNodeIDForActionID(action.id);
let buttonNode = document.getElementById(buttonNodeID);
let buttonNode = this.panelButtonNodeForActionID(action.id);
action.onShowingInPanel(buttonNode);
}
@ -738,22 +723,6 @@ var BrowserPageActions = {
});
},
/**
* Call this on the contextmenu event. Note that this is called before
* onContextMenuShowing.
*
* @param event (DOM event, required)
* The contextmenu event.
*/
onContextMenu(event) {
let node = event.originalTarget;
this._contextAction = this.actionForNode(node);
// Don't show the menu if there's no action where the user clicked!
if (!this._contextAction) {
event.preventDefault();
}
},
/**
* Call this on the context menu's popupshowing event.
*
@ -766,32 +735,58 @@ var BrowserPageActions = {
if (event.target != popup) {
return;
}
// Right now there's only one item in the context menu, to toggle the
// context action's shown-in-urlbar state. Update it now.
let toggleItem = popup.firstChild;
let toggleItemLabel = null;
if (this._contextAction) {
toggleItem.disabled = false;
if (this._contextAction.shownInUrlbar) {
toggleItemLabel = toggleItem.getAttribute("remove-label");
}
this._contextAction = this.actionForNode(popup.triggerNode);
if (!this._contextAction) {
event.preventDefault();
return;
}
if (!toggleItemLabel) {
toggleItemLabel = toggleItem.getAttribute("add-label");
let state;
if (this._contextAction._isBuiltIn) {
state =
this._contextAction.pinnedToUrlbar ?
"builtInPinned" :
"builtInUnpinned";
} else {
state =
this._contextAction.pinnedToUrlbar ?
"extensionPinned" :
"extensionUnpinned";
}
toggleItem.label = toggleItemLabel;
popup.setAttribute("state", state);
},
/**
* Call this from the context menu's toggle menu item.
* Call this from the menu item in the context menu that toggles pinning.
*/
toggleShownInUrlbarForContextAction() {
togglePinningForContextAction() {
if (!this._contextAction) {
return;
}
let telemetryType = this._contextAction.shownInUrlbar ? "removed" : "added";
PageActions.logTelemetry(telemetryType, this._contextAction);
this._contextAction.shownInUrlbar = !this._contextAction.shownInUrlbar;
let action = this._contextAction;
this._contextAction = null;
let telemetryType = action.pinnedToUrlbar ? "removed" : "added";
PageActions.logTelemetry(telemetryType, action);
action.pinnedToUrlbar = !action.pinnedToUrlbar;
},
/**
* Call this from the menu item in the context menu that opens about:addons.
*/
openAboutAddonsForContextAction() {
if (!this._contextAction) {
return;
}
let action = this._contextAction;
this._contextAction = null;
PageActions.logTelemetry("managed", action);
let viewID = "addons://detail/" + encodeURIComponent(action.extensionID);
window.BrowserOpenAddonsMgr(viewID);
},
_contextAction: null,

Просмотреть файл

@ -1416,6 +1416,17 @@ toolbarpaletteitem[place="palette"][hidden] {
}
}
/* Page action context menu */
#pageActionContextMenu > .pageActionContextMenuItem {
visibility: collapse;
}
#pageActionContextMenu[state=builtInPinned] > .pageActionContextMenuItem.builtInPinned,
#pageActionContextMenu[state=builtInUnpinned] > .pageActionContextMenuItem.builtInUnpinned,
#pageActionContextMenu[state=extensionPinned] > .pageActionContextMenuItem.extensionPinned,
#pageActionContextMenu[state=extensionUnpinned] > .pageActionContextMenuItem.extensionUnpinned {
visibility: visible;
}
/* WebExtension Sidebars */
#sidebar-box[sidebarcommand$="-sidebar-action"] > #sidebar-header > #sidebar-switcher-target > #sidebar-icon {
list-style-image: var(--webextension-menuitem-image, inherit);

Просмотреть файл

@ -422,7 +422,6 @@
viewCacheId="appMenu-viewCache">
<panelview id="pageActionPanelMainView"
context="pageActionContextMenu"
oncontextmenu="BrowserPageActions.onContextMenu(event);"
class="PanelUI-subView">
<vbox class="panel-subview-body"/>
</panelview>
@ -446,11 +445,22 @@
<menupopup id="pageActionContextMenu"
onpopupshowing="BrowserPageActions.onContextMenuShowing(event, this);">
<menuitem id="pageActionContextMenu-toggleUrlbar"
add-label="&pageAction.addToUrlbar.label;"
remove-label="&pageAction.removeFromUrlbar.label;"
<menuitem class="pageActionContextMenuItem builtInUnpinned"
label="&pageAction.addToUrlbar.label;"
oncommand="BrowserPageActions.toggleShownInUrlbarForContextAction();"/>
oncommand="BrowserPageActions.togglePinningForContextAction();"/>
<menuitem class="pageActionContextMenuItem builtInPinned"
label="&pageAction.removeFromUrlbar.label;"
oncommand="BrowserPageActions.togglePinningForContextAction();"/>
<menuitem class="pageActionContextMenuItem extensionUnpinned"
label="&pageAction.allowInUrlbar.label;"
oncommand="BrowserPageActions.togglePinningForContextAction();"/>
<menuitem class="pageActionContextMenuItem extensionPinned"
label="&pageAction.disallowInUrlbar.label;"
oncommand="BrowserPageActions.togglePinningForContextAction();"/>
<menuseparator class="pageActionContextMenuItem extensionPinned extensionUnpinned"/>
<menuitem class="pageActionContextMenuItem extensionPinned extensionUnpinned"
label="&pageAction.manageExtension.label;"
oncommand="BrowserPageActions.openAboutAddonsForContextAction();"/>
</menupopup>
<!-- Bookmarks and history tooltip -->
@ -859,9 +869,7 @@
<label id="switchtab" class="urlbar-display urlbar-display-switchtab" value="&urlbar.switchToTab.label;"/>
<label id="extension" class="urlbar-display urlbar-display-extension" value="&urlbar.extension.label;"/>
</box>
<hbox id="page-action-buttons"
context="pageActionContextMenu"
oncontextmenu="BrowserPageActions.onContextMenu(event);">
<hbox id="page-action-buttons" context="pageActionContextMenu">
<hbox id="userContext-icons" hidden="true">
<label id="userContext-label"/>
<image id="userContext-indicator"/>

Просмотреть файл

@ -289,6 +289,7 @@
<radio class="permPluginTemplateRadioDefault" label="&permUseDefault;"/>
<radio class="permPluginTemplateRadioAsk" label="&permAskAlways;"/>
<radio class="permPluginTemplateRadioAllow" label="&permAllow;"/>
<radio class="permPluginTemplateRadioHide" label="&permHide;"/>
<radio class="permPluginTemplateRadioBlock" label="&permBlock;"/>
</radiogroup>
</hbox>

Просмотреть файл

@ -183,6 +183,8 @@ function fillInPluginPermissionTemplate(aPluginName, aPermissionString) {
[ ".permPluginTemplateRadioDefault", "id", aPermissionString + "#0" ],
[ ".permPluginTemplateRadioAsk", "id", aPermissionString + "#3" ],
[ ".permPluginTemplateRadioAllow", "id", aPermissionString + "#1" ],
// #8 comes from Ci.nsIObjectLoadingContent.PLUGIN_PERMISSION_PROMPT_ACTION_QUIET
[ ".permPluginTemplateRadioHide", "id", aPermissionString + "#8"],
[ ".permPluginTemplateRadioBlock", "id", aPermissionString + "#2" ]
];

Просмотреть файл

@ -26,6 +26,7 @@ support-files =
plugin_hidden_to_visible.html
plugin_iframe.html
plugin_outsideScrollArea.html
plugin_overlay_styles.html
plugin_overlayed.html
plugin_positioned.html
plugin_simple_blank.swf
@ -71,6 +72,7 @@ tags = blocklist
tags = blocklist
[browser_CTP_outsideScrollArea.js]
tags = blocklist
[browser_CTP_overlay_styles.js]
[browser_CTP_remove_navigate.js]
tags = blocklist
[browser_CTP_resize.js]

Просмотреть файл

@ -82,8 +82,8 @@ add_task(async function() {
let doc = content.document;
let plugin = doc.getElementById("test");
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
Assert.ok(!overlay || !overlay.classList.contains("visible"),
"Test 3b, overlay should be hidden.");
Assert.ok(!overlay || overlay.getAttribute("sizing") == "blank",
"Test 3b, overlay should be blank.");
});
});
@ -109,8 +109,8 @@ add_task(async function() {
let doc = content.document;
let plugin = doc.getElementById("test");
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
Assert.ok(!overlay || !overlay.classList.contains("visible"),
"Test 4b, overlay should be hidden.");
Assert.ok(!overlay || overlay.getAttribute("sizing") == "blank",
"Test 4b, overlay should be blank.");
});
});

Просмотреть файл

@ -58,7 +58,7 @@ add_task(async function() {
let doc = content.document;
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
Assert.ok(overlay && overlay.classList.contains("visible") &&
!overlay.classList.contains("minimal"),
overlay.getAttribute("sizing") != "blank",
"Test 2, overlay should be visible.");
});
});
@ -88,7 +88,7 @@ add_task(async function() {
let doc = content.document;
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
Assert.ok(overlay && overlay.classList.contains("visible") &&
!overlay.classList.contains("minimal"),
overlay.getAttribute("sizing") != "blank",
"Test 3, overlay should be visible.");
});
});
@ -116,7 +116,7 @@ add_task(async function() {
let plugin = content.document.getElementById("test");
let doc = content.document;
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
Assert.ok(!overlay || !overlay.classList.contains("visible"),
"Test 4, overlay should be hidden.");
Assert.ok(!overlay || overlay.getAttribute("sizing") == "blank",
"Test 4, overlay should be blank.");
});
});

Просмотреть файл

@ -0,0 +1,93 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/* This test ensures that the click-to-play "Activate Plugin" overlay
* is shown in the right style (which is dependent on its size).
*/
const rootDir = getRootDirectory(gTestPath);
const gTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
var gTestBrowser = null;
const gTestcases = {
// 10x10
testcase1: {
sizing: "blank",
notext: null,
},
// 40x40
testcase2: {
sizing: "tiny",
notext: "notext",
},
// 100x70
testcase3: {
sizing: "reduced",
notext: "notext",
},
// 200x200
testcase4: {
sizing: null,
notext: "notext",
},
// 300x300
testcase5: {
sizing: null,
notext: null,
},
}
add_task(async function() {
registerCleanupFunction(function() {
clearAllPluginPermissions();
setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
gBrowser.removeCurrentTab();
gTestBrowser = null;
});
});
add_task(async function() {
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
gTestBrowser = gBrowser.selectedBrowser;
setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Test Plug-in");
let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
ok(!popupNotification, "Sanity check, should not have a click-to-play notification");
await promiseTabLoadEvent(gBrowser.selectedTab, gTestRoot + "plugin_overlay_styles.html");
// Work around for delayed PluginBindingAttached
await promiseUpdatePluginBindings(gTestBrowser);
await ContentTask.spawn(gTestBrowser, gTestcases, async function(testcases) {
let doc = content.document;
for (let testcaseId of Object.keys(testcases)) {
let plugin = doc.querySelector(`#${testcaseId} > object`);
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
Assert.ok(overlay, `overlay exists in ${testcaseId}`);
let expectations = testcases[testcaseId];
Assert.ok(overlay.classList.contains("visible") == true,
`The expected visibility is correct in ${testcaseId}`);
Assert.ok(overlay.getAttribute("sizing") == expectations.sizing,
`The expected sizing is correct in ${testcaseId}`);
Assert.ok(overlay.getAttribute("notext") == expectations.notext,
`The expected notext is correct in ${testcaseId}`);
}
});
});

Просмотреть файл

@ -46,8 +46,8 @@ add_task(async function() {
let doc = content.document;
let plugin = doc.getElementById("test");
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
Assert.ok(!overlay || overlay.classList.contains("minimal"),
"Test 2, overlay should be hidden.");
Assert.ok(!overlay || overlay.getAttribute("sizing") == "blank",
"Test 2, overlay should be blank.");
});
});
@ -64,8 +64,8 @@ add_task(async function() {
let doc = content.document;
let plugin = doc.getElementById("test");
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
Assert.ok(!overlay || overlay.classList.contains("minimal"),
"Test 3, overlay should be hidden.");
Assert.ok(!overlay || overlay.getAttribute("sizing") == "blank",
"Test 3, overlay should be blank.");
});
});
@ -84,7 +84,7 @@ add_task(async function() {
let doc = content.document;
let plugin = doc.getElementById("test");
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
Assert.ok(overlay && overlay.classList.contains("visible"),
Assert.ok(overlay && overlay.getAttribute("sizing") != "blank",
"Test 4, overlay should be visible.");
});
});
@ -104,8 +104,8 @@ add_task(async function() {
let doc = content.document;
let plugin = doc.getElementById("test");
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
Assert.ok(!overlay || overlay.classList.contains("minimal"),
"Test 5, overlay should be hidden.");
Assert.ok(!overlay || overlay.getAttribute("sizing") == "blank",
"Test 5, overlay should be blank.");
});
});
@ -124,7 +124,7 @@ add_task(async function() {
let doc = content.document;
let plugin = doc.getElementById("test");
let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main");
Assert.ok(overlay && overlay.classList.contains("visible"),
Assert.ok(overlay && overlay.getAttribute("sizing") != "blank",
"Test 6, overlay should be visible.");
});
});

Просмотреть файл

@ -7,6 +7,12 @@
/* This test ensures that the click-to-play "Activate Plugin" overlay
* is shown when expected.
* All testcases are in the plugin_shouldShowOverlay.html file.
*
* Note: Technically, the overlay is *always* shown. When this test was
* originally written, the meaning of "shown" was "shown with the contents",
* as opposed to "shown as blank". The behavior hasn't changed, but the naming
* has: now, a "shown as blank" overlay no longer receives a ".hidden" class.
* It receives a sizing="blank" attribute.
*/
var rootDir = getRootDirectory(gTestPath);
@ -49,7 +55,7 @@ add_task(async function() {
Assert.ok(overlay, `overlay exists in ${testcase.id}`);
let expectedVisibility = (testcase.getAttribute("shouldshow") == "true");
Assert.ok(overlay.classList.contains("visible") == expectedVisibility,
Assert.ok((overlay.getAttribute("sizing") != "blank") == expectedVisibility,
`The expected visibility is correct in ${testcase.id}`);
}
})

Просмотреть файл

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="testcase1" class="testcase">
<object width="10" height="10" type="application/x-test"></object>
</div>
<div id="testcase2" class="testcase">
<object width="40" height="40" type="application/x-test"></object>
</div>
<div id="testcase3" class="testcase">
<object width="100" height="70" type="application/x-test"></object>
</div>
<div id="testcase4" class="testcase">
<object width="200" height="200" type="application/x-test"></object>
</div>
<div id="testcase5" class="testcase">
<object width="300" height="300" type="application/x-test"></object>
</div>
</body>
</html>

Просмотреть файл

@ -4,6 +4,6 @@
<meta charset="utf-8">
</head>
<body>
<embed id="test" style="width: 200px; height: 200px" type="application/x-test">
<embed id="test" style="width: 300px; height: 300px" type="application/x-test">
</body>
</html>

Просмотреть файл

@ -122,9 +122,6 @@ var whitelist = [
{file: "resource://shield-recipe-client-content/shield-content-frame.js"},
{file: "resource://shield-recipe-client-content/shield-content-process.js"},
// New L10n API that is not yet used in production
{file: "chrome://global/content/l10n.js"},
// Starting from here, files in the whitelist are bugs that need fixing.
// Bug 1339424 (wontfix?)
{file: "chrome://browser/locale/taskbar.properties",

Просмотреть файл

@ -157,8 +157,8 @@ add_task(async function copyURLFromURLBar() {
await BrowserTestUtils.withNewTab(url, async () => {
// Add action to URL bar.
let action = PageActions._builtInActions.find(a => a.id == "copyURL");
action.shownInUrlbar = true;
registerCleanupFunction(() => action.shownInUrlbar = false);
action.pinnedToUrlbar = true;
registerCleanupFunction(() => action.pinnedToUrlbar = false);
let copyURLButton =
document.getElementById("pageAction-urlbar-copyURL");
@ -544,7 +544,7 @@ add_task(async function sendToDevice_inUrlbar() {
// Add Send to Device to the urlbar.
let action = PageActions.actionForID("sendToDevice");
action.shownInUrlbar = true;
action.pinnedToUrlbar = true;
// Click it to open its panel.
let urlbarButton = document.getElementById(
@ -622,7 +622,7 @@ add_task(async function sendToDevice_inUrlbar() {
await promisePanelHidden(BrowserPageActionFeedback.panelNode.id);
// Remove Send to Device from the urlbar.
action.shownInUrlbar = false;
action.pinnedToUrlbar = false;
cleanUp();
});
@ -642,14 +642,14 @@ add_task(async function contextMenu() {
});
await contextMenuPromise;
// The context menu should show "Remove from Address Bar". Click it.
let contextMenuNode = document.getElementById("pageActionContextMenu");
Assert.equal(contextMenuNode.childNodes.length, 1,
// The context menu should show the "remove" item. Click it.
let menuItems = collectContextMenuItems();
Assert.equal(menuItems.length, 1,
"Context menu has one child");
Assert.equal(contextMenuNode.childNodes[0].label, "Remove from Address Bar",
Assert.equal(menuItems[0].label, "Remove from Address Bar",
"Context menu is in the 'remove' state");
contextMenuPromise = promisePanelHidden("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(contextMenuNode.childNodes[0], {});
EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
await contextMenuPromise;
// The action should be removed from the urlbar. In this case, the bookmark
@ -668,13 +668,14 @@ add_task(async function contextMenu() {
});
await contextMenuPromise;
// The context menu should show "Add to Address Bar". Click it.
Assert.equal(contextMenuNode.childNodes.length, 1,
// The context menu should show the "add" item. Click it.
menuItems = collectContextMenuItems();
Assert.equal(menuItems.length, 1,
"Context menu has one child");
Assert.equal(contextMenuNode.childNodes[0].label, "Add to Address Bar",
Assert.equal(menuItems[0].label, "Add to Address Bar",
"Context menu is in the 'add' state");
contextMenuPromise = promisePanelHidden("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(contextMenuNode.childNodes[0], {});
EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
await contextMenuPromise;
// The action should be added to the urlbar.
@ -690,13 +691,14 @@ add_task(async function contextMenu() {
});
await contextMenuPromise;
// The context menu should show "Remove from Address Bar". Click it.
Assert.equal(contextMenuNode.childNodes.length, 1,
// The context menu should show the "remove" item. Click it.
menuItems = collectContextMenuItems();
Assert.equal(menuItems.length, 1,
"Context menu has one child");
Assert.equal(contextMenuNode.childNodes[0].label, "Remove from Address Bar",
Assert.equal(menuItems[0].label, "Remove from Address Bar",
"Context menu is in the 'remove' state");
contextMenuPromise = promisePanelHidden("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(contextMenuNode.childNodes[0], {});
EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
await contextMenuPromise;
// The action should be removed from the urlbar.
@ -713,12 +715,14 @@ add_task(async function contextMenu() {
button: 2,
});
await contextMenuPromise;
Assert.equal(contextMenuNode.childNodes.length, 1,
menuItems = collectContextMenuItems();
Assert.equal(menuItems.length, 1,
"Context menu has one child");
Assert.equal(contextMenuNode.childNodes[0].label, "Add to Address Bar",
Assert.equal(menuItems[0].label, "Add to Address Bar",
"Context menu is in the 'add' state");
contextMenuPromise = promisePanelHidden("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(contextMenuNode.childNodes[0], {});
EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
await contextMenuPromise;
await BrowserTestUtils.waitForCondition(() => {
return !starButtonBox.hidden;
@ -775,3 +779,10 @@ function checkSendToDeviceItems(expectedItems, forUrlbar = false) {
}
}
}
function collectContextMenuItems() {
let contextMenu = document.getElementById("pageActionContextMenu");
return Array.filter(contextMenu.childNodes, node => {
return window.getComputedStyle(node).visibility == "visible";
});
}

Просмотреть файл

@ -66,9 +66,10 @@ this.pageAction = class extends ExtensionAPI {
if (!this.browserPageAction) {
this.browserPageAction = PageActions.addAction(new PageActions.Action({
id: widgetId,
extensionID: extension.id,
title: this.defaults.title,
iconURL: this.getIconData(this.defaults.icon),
shownInUrlbar: true,
pinnedToUrlbar: true,
disabled: true,
onCommand: (event, buttonNode) => {
this.handleClick(event.target.ownerGlobal);

Просмотреть файл

@ -24,7 +24,7 @@ function makeWidgetId(id) {
return id.replace(/[^a-z0-9_-]/g, "_");
}
// Tests that the shownInUrlbar property of the PageActions.Action object
// Tests that the pinnedToUrlbar property of the PageActions.Action object
// backing the extension's page action persists across app restarts.
add_task(async function testAppShutdown() {
let extensionData = {
@ -44,11 +44,11 @@ add_task(async function testAppShutdown() {
let extension = ExtensionTestUtils.loadExtension(extensionData);
await extension.startup();
// Get the PageAction.Action object. Its shownInUrlbar should have been
// Get the PageAction.Action object. Its pinnedToUrlbar should have been
// initialized to true in ext-pageAction.js, when it's created.
let actionID = makeWidgetId(extension.id);
let action = PageActions.actionForID(actionID);
Assert.equal(action.shownInUrlbar, true);
Assert.equal(action.pinnedToUrlbar, true);
// Simulate restarting the app without first unloading the extension.
await promiseShutdownManager();
@ -56,12 +56,12 @@ add_task(async function testAppShutdown() {
await promiseStartupManager();
await extension.awaitStartup();
// Get the action. Its shownInUrlbar should remain true.
// Get the action. Its pinnedToUrlbar should remain true.
action = PageActions.actionForID(actionID);
Assert.equal(action.shownInUrlbar, true);
Assert.equal(action.pinnedToUrlbar, true);
// Now set its shownInUrlbar to false.
action.shownInUrlbar = false;
// Now set its pinnedToUrlbar to false.
action.pinnedToUrlbar = false;
// Simulate restarting the app again without first unloading the extension.
await promiseShutdownManager();
@ -69,9 +69,9 @@ add_task(async function testAppShutdown() {
await promiseStartupManager();
await extension.awaitStartup();
// Get the action. Its shownInUrlbar should remain false.
// Get the action. Its pinnedToUrlbar should remain false.
action = PageActions.actionForID(actionID);
Assert.equal(action.shownInUrlbar, false);
Assert.equal(action.pinnedToUrlbar, false);
// Now unload the extension and quit the app.
await extension.unload();

Просмотреть файл

@ -1186,12 +1186,28 @@ var gMainPane = {
let processCountPref = document.getElementById("dom.ipc.processCount");
let defaultProcessCount = processCountPref.defaultValue;
let bundlePreferences = document.getElementById("bundlePreferences");
let label = bundlePreferences.getFormattedString("defaultContentProcessCount",
[defaultProcessCount]);
let contentProcessCount =
document.querySelector(`#contentProcessCount > menupopup >
menuitem[value="${defaultProcessCount}"]`);
contentProcessCount.label = label;
// New localization API experiment (October 2017).
// See bug 1402061 for details.
//
// The `intl.l10n.fluent.disabled` pref can be used
// to opt-out of the experiment in case of any
// unforseen problems. The legacy API will then
// be used.
if (Services.prefs.getBoolPref("intl.l10n.fluent.disabled", false)) {
let label = bundlePreferences.getFormattedString("defaultContentProcessCount",
[defaultProcessCount]);
contentProcessCount.label = label;
} else {
document.l10n.setAttributes(
contentProcessCount,
"default-content-process-count",
{ num: defaultProcessCount });
}
document.getElementById("limitContentProcess").disabled = false;
document.getElementById("contentProcessCount").disabled = false;

Просмотреть файл

@ -4,6 +4,9 @@
<!-- General panel -->
<link rel="localization" href="browser/preferences/main.ftl"/>
<script type="text/javascript" src="chrome://global/content/l10n.js"></script>
<script type="application/javascript"
src="chrome://browser/content/preferences/in-content/main.js"/>

Просмотреть файл

@ -176,7 +176,9 @@
<hbox class="help-button" pack="center">
<label class="text-link">
<image class="help-icon"/><label class="help-label" flex="1">&helpButton2.label;</label>
<hbox align="center">
<image class="help-icon"/><label class="help-label" flex="1">&helpButton2.label;</label>
</hbox>
</label>
</hbox>
</vbox>

Просмотреть файл

@ -85,3 +85,4 @@ support-files =
skip-if = !healthreport || !telemetry || (os == 'linux' && debug) || (os == 'android')
[browser_containers_name_input.js]
run-if = nightly_build # Containers is enabled only on Nightly
[browser_fluent.js]

Просмотреть файл

@ -0,0 +1,48 @@
/* eslint-disable mozilla/no-cpows-in-tests */
function whenMainPaneLoadedFinished() {
return new Promise(function(resolve, reject) {
const topic = "main-pane-loaded";
Services.obs.addObserver(function observer(aSubject) {
Services.obs.removeObserver(observer, topic);
resolve();
}, topic);
});
}
// Temporary test for an experimental new localization API.
// See bug 1402069 for details.
add_task(async function() {
// The string is used only when `browserTabsRemoteAutostart` is true
if (!Services.appinfo.browserTabsRemoteAutostart) {
ok(true, "fake test to avoid harness complaining");
return;
}
await Promise.all([
openPreferencesViaOpenPreferencesAPI("general", {leaveOpen: true}),
whenMainPaneLoadedFinished(),
]);
let doc = gBrowser.contentDocument;
await doc.l10n.ready;
let processCountPref = doc.getElementById("dom.ipc.processCount");
let defaultProcessCount = processCountPref.defaultValue;
let [ msg ] = await doc.l10n.formatMessages([
["default-content-process-count", { num: defaultProcessCount }]
]);
let elem = doc.querySelector(
`#contentProcessCount > menupopup > menuitem[value="${defaultProcessCount}"]`);
Assert.deepEqual(msg, {
value: null,
attrs: [
["label", elem.getAttribute("label")]
]
});
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

Просмотреть файл

@ -136,8 +136,8 @@ var pageActionsHelper = {
setActionsUrlbarState(inUrlbar) {
this._originalStates = [];
PageActions._actionsByID.forEach(action => {
this._originalStates.push([ action, action.shownInUrlbar ]);
action.shownInUrlbar = inUrlbar;
this._originalStates.push([ action, action.pinnedToUrlbar ]);
action.pinnedToUrlbar = inUrlbar;
});
},
@ -146,7 +146,7 @@ var pageActionsHelper = {
return;
}
for (let [ action, originalState] of this._originalStates) {
action.shownInUrlbar = originalState;
action.pinnedToUrlbar = originalState;
}
this._originalStates = null;
}

Просмотреть файл

@ -46,7 +46,7 @@ const CONTENT = {
},
mainAction: {
label: GetStringFromName(changeAutofillOptsKey),
accessKey: "C",
accessKey: GetStringFromName("changeAutofillOptionsAccessKey"),
callbackState: "open-pref",
disableHighlight: true,
},
@ -82,12 +82,12 @@ const CONTENT = {
},
mainAction: {
label: GetStringFromName("updateAddressLabel"),
accessKey: "U",
accessKey: GetStringFromName("updateAddressAccessKey"),
callbackState: "update",
},
secondaryActions: [{
label: GetStringFromName("createAddressLabel"),
accessKey: "C",
accessKey: GetStringFromName("createAddressAccessKey"),
callbackState: "create",
}],
options: {
@ -107,16 +107,16 @@ const CONTENT = {
},
mainAction: {
label: GetStringFromName("saveCreditCardLabel"),
accessKey: "S",
accessKey: GetStringFromName("saveCreditCardAccessKey"),
callbackState: "save",
},
secondaryActions: [{
label: GetStringFromName("cancelCreditCardLabel"),
accessKey: "D",
accessKey: GetStringFromName("cancelCreditCardAccessKey"),
callbackState: "cancel",
}, {
label: GetStringFromName("neverSaveCreditCardLabel"),
accessKey: "N",
accessKey: GetStringFromName("neverSaveCreditCardAccessKey"),
callbackState: "disable",
}],
options: {
@ -160,12 +160,12 @@ const CONTENT = {
},
mainAction: {
label: GetStringFromName("updateCreditCardLabel"),
accessKey: "U",
accessKey: GetStringFromName("updateCreditCardAccessKey"),
callbackState: "update",
},
secondaryActions: [{
label: GetStringFromName("createCreditCardLabel"),
accessKey: "C",
accessKey: GetStringFromName("createCreditCardAccessKey"),
callbackState: "create",
}],
options: {

Просмотреть файл

@ -17,6 +17,7 @@ autofillSecurityOptionsLinkOSX = Form Autofill & Security Preferences
# that notifies users that addresses are saved. The button leads users to Form Autofill browser preferences.
changeAutofillOptions = Change Form Autofill Options
changeAutofillOptionsOSX = Change Form Autofill Preferences
changeAutofillOptionsAccessKey = C
# LOCALIZATION NOTE (addressesSyncCheckbox): If Sync is enabled, this checkbox is displayed on the doorhanger
# shown when saving addresses.
addressesSyncCheckbox = Share addresses with synced devices
@ -27,19 +28,26 @@ creditCardsSyncCheckbox = Share credit cards with synced devices
# when an address change is detected.
updateAddressMessage = Would you like to update your address with this new information?
createAddressLabel = Create New Address
createAddressAccessKey = C
updateAddressLabel = Update Address
updateAddressAccessKey = U
# LOCALIZATION NOTE (saveCreditCardMessage, saveCreditCardLabel, cancelCreditCardLabel, neverSaveCreditCardLabel):
# Used on the doorhanger when users submit payment with credit card.
# LOCALIZATION NOTE (saveCreditCardMessage): %S is brandShortName.
saveCreditCardMessage = Would you like %S to save this credit card? (Security code will not be saved)
saveCreditCardLabel = Save Credit Card
saveCreditCardAccessKey = S
cancelCreditCardLabel = Dont Save
cancelCreditCardAccessKey = D
neverSaveCreditCardLabel = Never Save Credit Cards
neverSaveCreditCardAccessKey = N
# LOCALIZATION NOTE (updateCreditCardMessage, createCreditCardLabel, updateCreditCardLabel): Used on the doorhanger
# when an credit card change is detected.
updateCreditCardMessage = Would you like to update your credit card with this new information?
createCreditCardLabel = Create New Credit Card
createCreditCardAccessKey = C
updateCreditCardLabel = Update Credit Card
updateCreditCardAccessKey = U
# LOCALIZATION NOTE (openAutofillMessagePanel): Tooltip label for Form Autofill doorhanger icon on address bar.
openAutofillMessagePanel = Open Form Autofill message panel

2
browser/extensions/pocket/bootstrap.js поставляемый
Просмотреть файл

@ -94,7 +94,7 @@ var PocketPageAction = {
this.pageAction = PageActions.addAction(new PageActions.Action({
id,
title: gPocketBundle.GetStringFromName("saveToPocketCmd.label"),
shownInUrlbar: true,
pinnedToUrlbar: true,
wantsIframe: true,
urlbarIDOverride: "pocket-button-box",
anchorIDOverride: "pocket-button",

Просмотреть файл

@ -46,6 +46,7 @@
[@AB_CD@]
@RESPATH@/dictionaries/*
@RESPATH@/browser/localization/*
#if defined(XP_WIN) || defined(XP_LINUX)
@RESPATH@/fonts/*
#endif

Просмотреть файл

@ -96,6 +96,7 @@ uk
ur
uz
vi
wo
xh
zh-CN
zh-TW

Просмотреть файл

@ -0,0 +1,4 @@
// Variables:
// $num - default value of the `dom.ipc.processCount` pref.
default-content-process-count
.label = { $num } (default)

Просмотреть файл

@ -961,6 +961,9 @@ you can use these alternative items. Otherwise, their values should be empty. -
<!ENTITY pageActionButton.tooltip "Page actions">
<!ENTITY pageAction.addToUrlbar.label "Add to Address Bar">
<!ENTITY pageAction.removeFromUrlbar.label "Remove from Address Bar">
<!ENTITY pageAction.allowInUrlbar.label "Show in Address Bar">
<!ENTITY pageAction.disallowInUrlbar.label "Dont Show in Address Bar">
<!ENTITY pageAction.manageExtension.label "Manage Extension…">
<!ENTITY pageAction.sendTabToDevice.label "Send Tab to Device">
<!ENTITY sendToDevice.syncNotReady.label "Syncing Devices…">

Просмотреть файл

@ -55,6 +55,7 @@
<!ENTITY permAskAlways "Always ask">
<!ENTITY permAllow "Allow">
<!ENTITY permAllowSession "Allow for Session">
<!ENTITY permHide "Hide">
<!ENTITY permBlock "Block">
<!ENTITY permissionsFor "Permissions for:">
<!ENTITY permPlugins "Activate Plugins">

Просмотреть файл

@ -7,6 +7,9 @@
# override and resource entries should go to browser/base/jar.mn to avoid
# having to create the same entry for each locale.
[localization] @AB_CD@.jar:
browser (%browser/**/*.ftl)
@AB_CD@.jar:
% locale browser @AB_CD@ %locale/browser/
* locale/browser/bookmarks.html (generic/profile/bookmarks.html.in)

Просмотреть файл

@ -103,6 +103,7 @@ locales = [
"ur",
"uz",
"vi",
"wo",
"xh",
"zh-CN",
"zh-TW",

Просмотреть файл

@ -119,15 +119,20 @@ this.PageActions = {
},
/**
* The list of actions in the urlbar, sorted in the order in which they should
* appear there. Not live. (array of Action objects)
* The list of actions currently in the urlbar, sorted in the order in which
* they appear. Not live.
*
* @param browserWindow (DOM window, required)
* This window's actions will be returned.
* @return (array of PageAction.Action objects) The actions currently in the
* given window's urlbar.
*/
get actionsInUrlbar() {
actionsInUrlbar(browserWindow) {
// Remember that IDs in idsInUrlbar may belong to actions that aren't
// currently registered.
return this._persistedActions.idsInUrlbar.reduce((actions, id) => {
let action = this.actionForID(id);
if (action) {
if (action && action.shouldShowInUrlbar(browserWindow)) {
actions.push(action);
}
return actions;
@ -228,30 +233,29 @@ this.PageActions = {
}
if (this._persistedActions.ids.includes(action.id)) {
// The action has been seen before. Override its shownInUrlbar value
// The action has been seen before. Override its pinnedToUrlbar value
// with the persisted value. Set the private version of that property
// so that onActionToggledShownInUrlbar isn't called, which happens when
// so that onActionToggledPinnedToUrlbar isn't called, which happens when
// the public version is set.
action._shownInUrlbar =
action._pinnedToUrlbar =
this._persistedActions.idsInUrlbar.includes(action.id);
} else {
// The action is new. Store it in the persisted actions.
this._persistedActions.ids.push(action.id);
this._updateIDsInUrlbarForAction(action);
this._updateIDsPinnedToUrlbarForAction(action);
}
},
_updateIDsInUrlbarForAction(action) {
_updateIDsPinnedToUrlbarForAction(action) {
let index = this._persistedActions.idsInUrlbar.indexOf(action.id);
if (action.shownInUrlbar) {
if (action.pinnedToUrlbar) {
if (index < 0) {
let nextID = this.nextActionIDInUrlbar(action.id);
let nextIndex =
nextID ? this._persistedActions.idsInUrlbar.indexOf(nextID) : -1;
if (nextIndex < 0) {
nextIndex = this._persistedActions.idsInUrlbar.length;
index = action.id == ACTION_ID_BOOKMARK ? -1 :
this._persistedActions.idsInUrlbar.indexOf(ACTION_ID_BOOKMARK);
if (index < 0) {
index = this._persistedActions.idsInUrlbar.length;
}
this._persistedActions.idsInUrlbar.splice(nextIndex, 0, action.id);
this._persistedActions.idsInUrlbar.splice(index, 0, action.id);
}
} else if (index >= 0) {
this._persistedActions.idsInUrlbar.splice(index, 1);
@ -273,13 +277,13 @@ this.PageActions = {
* @return The ID of the reference action, or null if your action should be
* appended.
*/
nextActionIDInUrlbar(action) {
nextActionIDInUrlbar(browserWindow, action) {
// Actions in the urlbar are always inserted before the bookmark action,
// which always comes last if it's present.
if (action.id == ACTION_ID_BOOKMARK) {
return null;
}
let id = this._nextActionID(action, this.actionsInUrlbar);
let id = this._nextActionID(action, this.actionsInUrlbar(browserWindow));
return id || ACTION_ID_BOOKMARK;
},
@ -354,34 +358,32 @@ this.PageActions = {
},
/**
* Call this when an action's shownInUrlbar property changes.
* Call this when an action's pinnedToUrlbar property changes.
*
* @param action (Action object, required)
* The action whose shownInUrlbar property changed.
* The action whose pinnedToUrlbar property changed.
*/
onActionToggledShownInUrlbar(action) {
onActionToggledPinnedToUrlbar(action) {
if (!this.actionForID(action.id)) {
// This may be called before the action has been added.
return;
}
this._updateIDsInUrlbarForAction(action);
this._updateIDsPinnedToUrlbarForAction(action);
for (let bpa of allBrowserPageActions()) {
bpa.placeActionInUrlbar(action);
}
},
logTelemetry(type, action, node = null) {
const kAllowedLabels = ["pocket", "screenshots", "webcompat"].concat(
gBuiltInActions.filter(a => !a.__isSeparator).map(a => a.id)
);
if (type == "used") {
type = (node && node.closest("#urlbar-container")) ? "urlbar_used" : "panel_used";
type =
node && node.closest("#urlbar-container") ? "urlbar_used" :
"panel_used";
}
let histogramID = "FX_PAGE_ACTION_" + type.toUpperCase();
try {
let histogram = Services.telemetry.getHistogramById(histogramID);
if (kAllowedLabels.includes(action.labelForHistogram)) {
if (action._isBuiltIn) {
histogram.add(action.labelForHistogram);
} else {
histogram.add("other");
@ -492,6 +494,8 @@ this.PageActions = {
* @param disabled (bool, optional)
* Pass true to cause the action to be disabled initially in all browser
* windows. False by default.
* @param extensionID (string, optional)
* If the action lives in an extension, pass its ID.
* @param iconURL (string or object, optional)
* The URL string of the action's icon. Usually you want to specify an
* icon in CSS, but this option is useful if that would be a pain for
@ -549,9 +553,9 @@ this.PageActions = {
* Called when a browser window's page action panel is showing:
* onShowingInPanel(buttonNode)
* * buttonNode: The action's node in the page action panel.
* @param shownInUrlbar (bool, optional)
* Pass true to show the action in the urlbar, false otherwise. False by
* default.
* @param pinnedToUrlbar (bool, optional)
* Pass true to pin the action to the urlbar. An action is shown in the
* urlbar if it's pinned and not disabled. False by default.
* @param subview (object, optional)
* An options object suitable for passing to the Subview constructor, if
* you'd like the action to have a subview. See the subview constructor
@ -572,6 +576,7 @@ function Action(options) {
title: !options._isSeparator,
anchorIDOverride: false,
disabled: false,
extensionID: false,
iconURL: false,
labelForHistogram: false,
nodeAttributes: false,
@ -585,7 +590,7 @@ function Action(options) {
onPlacedInUrlbar: false,
onRemovedFromWindow: false,
onShowingInPanel: false,
shownInUrlbar: false,
pinnedToUrlbar: false,
subview: false,
tooltip: false,
urlbarIDOverride: false,
@ -617,6 +622,13 @@ function Action(options) {
}
Action.prototype = {
/**
* The ID of the action's parent extension (string, nullable)
*/
get extensionID() {
return this._extensionID;
},
/**
* The action's ID (string, nonnull)
*/
@ -633,17 +645,18 @@ Action.prototype = {
},
/**
* True if the action is shown in the urlbar (bool, nonnull)
* True if the action is pinned to the urlbar. The action is shown in the
* urlbar if it's pinned and not disabled. (bool, nonnull)
*/
get shownInUrlbar() {
return this._shownInUrlbar || false;
get pinnedToUrlbar() {
return this._pinnedToUrlbar || false;
},
set shownInUrlbar(shown) {
if (this.shownInUrlbar != shown) {
this._shownInUrlbar = shown;
PageActions.onActionToggledShownInUrlbar(this);
set pinnedToUrlbar(shown) {
if (this.pinnedToUrlbar != shown) {
this._pinnedToUrlbar = shown;
PageActions.onActionToggledPinnedToUrlbar(this);
}
return this.shownInUrlbar;
return this.pinnedToUrlbar;
},
/**
@ -972,7 +985,28 @@ Action.prototype = {
*/
remove() {
PageActions.onActionRemoved(this);
}
},
/**
* Returns whether the action should be shown in a given window's urlbar.
*
* @param browserWindow (DOM window, required)
* The window.
* @return True if the action should be shown and false otherwise. The action
* should be shown if it's both pinned and not disabled.
*/
shouldShowInUrlbar(browserWindow) {
return this.pinnedToUrlbar && !this.getDisabled(browserWindow);
},
get _isBuiltIn() {
let builtInIDs = [
"pocket",
"screenshots",
"webcompat-reporter-button",
].concat(gBuiltInActions.filter(a => !a.__isSeparator).map(a => a.id));
return builtInIDs.includes(this.id);
},
};
this.PageActions.Action = Action;
@ -1146,7 +1180,7 @@ var gBuiltInActions = [
// The title is set in browser-pageActions.js by calling
// BookmarkingUI.updateBookmarkPageMenuItem().
title: "",
shownInUrlbar: true,
pinnedToUrlbar: true,
nodeAttributes: {
observes: "bookmarkThisPageBroadcaster",
},

Просмотреть файл

@ -30,9 +30,14 @@ this.PluginContent = function(global) {
const FLASH_MIME_TYPE = "application/x-shockwave-flash";
const REPLACEMENT_STYLE_SHEET = Services.io.newURI("chrome://pluginproblem/content/pluginReplaceBinding.css");
const OVERLAY_DISPLAY_HIDDEN = 0;
const OVERLAY_DISPLAY_VISIBLE = 1;
const OVERLAY_DISPLAY_MINIMAL = 2;
const OVERLAY_DISPLAY = {
HIDDEN: 0, // The overlay will be transparent
BLANK: 1, // The overlay will be just a grey box
TINY: 2, // The overlay with a 16x16 plugin icon
REDUCED: 3, // The overlay with a 32x32 plugin icon
NOTEXT: 4, // The overlay with a 48x48 plugin icon and the close button
FULL: 5, // The full overlay: 48x48 plugin icon, close button and label
};
PluginContent.prototype = {
init(global) {
@ -289,44 +294,80 @@ PluginContent.prototype = {
* Update the visibility of the plugin overlay.
*/
setVisibility(plugin, overlay, overlayDisplayState) {
overlay.classList.toggle("visible", overlayDisplayState != OVERLAY_DISPLAY_HIDDEN);
overlay.classList.toggle("minimal", overlayDisplayState == OVERLAY_DISPLAY_MINIMAL)
if (overlayDisplayState == OVERLAY_DISPLAY_VISIBLE) {
overlay.classList.toggle("visible", overlayDisplayState != OVERLAY_DISPLAY.HIDDEN);
if (overlayDisplayState != OVERLAY_DISPLAY.HIDDEN) {
overlay.removeAttribute("dismissed");
}
},
/**
* Check whether the plugin should be visible on the page. A plugin should
* not be visible if the overlay is too big, or if any other page content
* overlays it.
* Adjust the style in which the overlay will be displayed. It might be adjusted
* based on its size, or if there's some other element covering all corners of
* the overlay.
*
* This function will handle showing or hiding the overlay.
* @returns true if the plugin is invisible.
* This function will handle adjusting the style of the overlay, but will
* not handle hiding it. That is done by setVisibility with the return value
* from this function.
*
* @returns A value from OVERLAY_DISPLAY.
*/
computeOverlayDisplayState(plugin, overlay) {
computeAndAdjustOverlayDisplay(plugin, overlay) {
let fallbackType = plugin.pluginFallbackType;
if (plugin.pluginFallbackTypeOverride !== undefined) {
fallbackType = plugin.pluginFallbackTypeOverride;
}
if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY_QUIET) {
return OVERLAY_DISPLAY_HIDDEN;
return OVERLAY_DISPLAY.HIDDEN;
}
// If the overlay size is 0, we haven't done layout yet. Presume that
// plugins are visible until we know otherwise.
if (overlay.scrollWidth == 0) {
return OVERLAY_DISPLAY_VISIBLE;
return OVERLAY_DISPLAY.FULL;
}
let overlayDisplay = OVERLAY_DISPLAY.FULL;
// Is the <object>'s size too small to hold what we want to show?
let pluginRect = plugin.getBoundingClientRect();
let pluginWidth = Math.ceil(pluginRect.width);
let pluginHeight = Math.ceil(pluginRect.height);
// We must set the attributes while here inside this function in order
// for a possible re-style to occur, which will make the scrollWidth/Height
// checks below correct. Otherwise, we would be requesting e.g. a TINY
// overlay here, but the default styling would be used, and that would make
// it overflow, causing it to change to BLANK instead of remaining as TINY.
if (pluginWidth <= 32 || pluginHeight <= 32) {
overlay.setAttribute("sizing", "blank");
overlayDisplay = OVERLAY_DISPLAY.BLANK;
} else if (pluginWidth <= 80 || pluginHeight <= 60) {
overlayDisplay = OVERLAY_DISPLAY.TINY;
overlay.setAttribute("sizing", "tiny");
overlay.setAttribute("notext", "notext");
} else if (pluginWidth <= 120 || pluginHeight <= 80) {
overlayDisplay = OVERLAY_DISPLAY.REDUCED;
overlay.setAttribute("sizing", "reduced");
overlay.setAttribute("notext", "notext");
} else if (pluginWidth <= 240 || pluginHeight <= 160) {
overlayDisplay = OVERLAY_DISPLAY.NOTEXT;
overlay.removeAttribute("sizing");
overlay.setAttribute("notext", "notext");
} else {
overlayDisplay = OVERLAY_DISPLAY.FULL;
overlay.removeAttribute("sizing");
overlay.removeAttribute("notext");
}
// XXX bug 446693. The text-shadow on the submitted-report text at
// the bottom causes scrollHeight to be larger than it should be.
let overflows = (overlay.scrollWidth > Math.ceil(pluginRect.width)) ||
(overlay.scrollHeight - 5 > Math.ceil(pluginRect.height));
let overflows = (overlay.scrollWidth > pluginWidth) ||
(overlay.scrollHeight - 5 > pluginHeight);
if (overflows) {
return OVERLAY_DISPLAY_MINIMAL;
overlay.setAttribute("sizing", "blank");
return OVERLAY_DISPLAY.BLANK;
}
// Is the plugin covered up by other content so that it is not clickable?
@ -353,11 +394,12 @@ PluginContent.prototype = {
}
let el = cwu.elementFromPoint(x, y, true, true);
if (el === plugin) {
return OVERLAY_DISPLAY_VISIBLE;
return overlayDisplay;
}
}
return OVERLAY_DISPLAY_HIDDEN;
overlay.setAttribute("sizing", "blank");
return OVERLAY_DISPLAY.BLANK;
},
addLinkClickCallback(linkNode, callbackName /* callbackArgs...*/) {
@ -476,7 +518,7 @@ PluginContent.prototype = {
if (eventType == "PluginPlaceholderReplaced") {
plugin.removeAttribute("href");
let overlay = this.getPluginUI(plugin, "main");
this.setVisibility(plugin, overlay, OVERLAY_DISPLAY_VISIBLE);
this.setVisibility(plugin, overlay, OVERLAY_DISPLAY.FULL);
let inIDOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
.getService(Ci.inIDOMUtils);
// Add psuedo class so our styling will take effect
@ -561,10 +603,10 @@ PluginContent.prototype = {
if (eventType != "PluginCrashed") {
if (overlay != null) {
this.setVisibility(plugin, overlay,
this.computeOverlayDisplayState(plugin, overlay));
this.computeAndAdjustOverlayDisplay(plugin, overlay));
let resizeListener = () => {
this.setVisibility(plugin, overlay,
this.computeOverlayDisplayState(plugin, overlay));
this.computeAndAdjustOverlayDisplay(plugin, overlay));
this.updateNotificationUI();
};
plugin.addEventListener("overflow", resizeListener);
@ -903,9 +945,9 @@ PluginContent.prototype = {
if (!overlay) {
continue;
}
let overlayDisplayState = this.computeOverlayDisplayState(plugin, overlay);
let overlayDisplayState = this.computeAndAdjustOverlayDisplay(plugin, overlay);
this.setVisibility(plugin, overlay, overlayDisplayState);
if (overlayDisplayState == OVERLAY_DISPLAY_VISIBLE) {
if (overlayDisplayState > OVERLAY_DISPLAY.BLANK) {
actions.delete(info.permissionString);
if (actions.size == 0) {
break;
@ -1089,21 +1131,21 @@ PluginContent.prototype = {
let link = this.getPluginUI(plugin, "reloadLink");
this.addLinkClickCallback(link, "reloadPage");
let overlayDisplayState = this.computeOverlayDisplayState(plugin, overlay);
let overlayDisplayState = this.computeAndAdjustOverlayDisplay(plugin, overlay);
// Is the <object>'s size too small to hold what we want to show?
if (overlayDisplayState != OVERLAY_DISPLAY_VISIBLE) {
if (overlayDisplayState != OVERLAY_DISPLAY.FULL) {
// First try hiding the crash report submission UI.
statusDiv.removeAttribute("status");
overlayDisplayState = this.computeOverlayDisplayState(plugin, overlay);
overlayDisplayState = this.computeAndAdjustOverlayDisplay(plugin, overlay);
}
this.setVisibility(plugin, overlay, overlayDisplayState);
let doc = plugin.ownerDocument;
let runID = plugin.runID;
if (overlayDisplayState == OVERLAY_DISPLAY_VISIBLE) {
if (overlayDisplayState == OVERLAY_DISPLAY.FULL) {
// If a previous plugin on the page was too small and resulted in adding a
// notification bar, then remove it because this plugin instance it big
// enough to serve as in-content notification.

Просмотреть файл

@ -1,4 +1,3 @@
/* eslint-disable mozilla/no-arbitrary-setTimeout */
"use strict";
// This is a test for PageActions.jsm, specifically the generalized parts that
@ -77,10 +76,14 @@ add_task(async function simple() {
Assert.equal(action.getIconURL(), iconURL, "iconURL");
Assert.equal(action.id, id, "id");
Assert.deepEqual(action.nodeAttributes, nodeAttributes, "nodeAttributes");
Assert.equal(action.shownInUrlbar, false, "shownInUrlbar");
Assert.equal(action.pinnedToUrlbar, false, "pinnedToUrlbar");
Assert.equal(action.subview, null, "subview");
Assert.equal(action.getDisabled(), false, "disabled");
Assert.equal(action.getDisabled(window), false, "disabled in window");
Assert.equal(action.getTitle(), title, "title");
Assert.equal(action.getTitle(window), title, "title in window");
Assert.equal(action.getTooltip(), tooltip, "tooltip");
Assert.equal(action.getTooltip(window), tooltip, "tooltip in window");
Assert.equal(action.urlbarIDOverride, null, "urlbarIDOverride");
Assert.equal(action.wantsIframe, false, "wantsIframe");
@ -151,7 +154,7 @@ add_task(async function simple() {
Assert.equal(onCommandCallCount, 1, "onCommandCallCount should be inc'ed");
// Show the action's button in the urlbar.
action.shownInUrlbar = true;
action.pinnedToUrlbar = true;
Assert.equal(onPlacedInUrlbarCallCount, 1,
"onPlacedInUrlbarCallCount should be inc'ed");
urlbarButtonNode = document.getElementById(urlbarButtonID);
@ -172,6 +175,22 @@ add_task(async function simple() {
"Next node should be the bookmark star"
);
// Disable the action. The button in the urlbar should be removed, and the
// button in the panel should be disabled.
action.setDisabled(true);
urlbarButtonNode = document.getElementById(urlbarButtonID);
Assert.equal(urlbarButtonNode, null, "urlbar button should be removed");
Assert.equal(panelButtonNode.disabled, true,
"panel button should be disabled");
// Enable the action. The button in the urlbar should be added back, and the
// button in the panel should be enabled.
action.setDisabled(false);
urlbarButtonNode = document.getElementById(urlbarButtonID);
Assert.notEqual(urlbarButtonNode, null, "urlbar button should be added back");
Assert.equal(panelButtonNode.disabled, false,
"panel button should not be disabled");
// Click the urlbar button.
onCommandExpectedButtonID = urlbarButtonID;
EventUtils.synthesizeMouseAtCenter(urlbarButtonNode, {});
@ -184,7 +203,7 @@ add_task(async function simple() {
Assert.equal(panelButtonNode.getAttribute("label"), action.getTitle(),
"New label");
// Now that shownInUrlbar has been toggled, make sure that it sticks across
// Now that pinnedToUrlbar has been toggled, make sure that it sticks across
// app restarts. Simulate that by "unregistering" the action (not by removing
// it, which is more permanent) and then registering it again.
@ -202,10 +221,10 @@ add_task(async function simple() {
"PageActions should have 'seen' the action");
Assert.ok(PageActions._persistedActions.idsInUrlbar.includes(action.id),
"idsInUrlbar should still include the action");
Assert.ok(action.shownInUrlbar,
"shownInUrlbar should still be true");
Assert.ok(action._shownInUrlbar,
"_shownInUrlbar should still be true, for good measure");
Assert.ok(action.pinnedToUrlbar,
"pinnedToUrlbar should still be true");
Assert.ok(action._pinnedToUrlbar,
"_pinnedToUrlbar should still be true, for good measure");
// Remove the action.
action.remove();
@ -301,7 +320,7 @@ add_task(async function withSubview() {
let action = PageActions.addAction(new PageActions.Action({
iconURL: "chrome://browser/skin/mail.svg",
id,
shownInUrlbar: true,
pinnedToUrlbar: true,
subview,
title: "Test subview",
onCommand(event, buttonNode) {
@ -444,7 +463,7 @@ add_task(async function withIframe() {
let action = PageActions.addAction(new PageActions.Action({
iconURL: "chrome://browser/skin/mail.svg",
id,
shownInUrlbar: true,
pinnedToUrlbar: true,
title: "Test iframe",
wantsIframe: true,
onCommand(event, buttonNode) {
@ -530,7 +549,7 @@ add_task(async function withIframe() {
await promisePanelHidden(BrowserPageActions._activatedActionPanelID);
// Hide the action's button in the urlbar.
action.shownInUrlbar = false;
action.pinnedToUrlbar = false;
urlbarButtonNode = document.getElementById(urlbarButtonID);
Assert.equal(urlbarButtonNode, null, "urlbarButtonNode");
@ -830,7 +849,7 @@ add_task(async function urlbarOrderNewWindow() {
return PageActions.addAction(new PageActions.Action({
id: `test-urlbarOrderNewWindow-${i}`,
title: `Test urlbarOrderNewWindow ${i}`,
shownInUrlbar: true,
pinnedToUrlbar: true,
}));
});
@ -844,8 +863,8 @@ add_task(async function urlbarOrderNewWindow() {
"PageActions._persistedActions.idsInUrlbar has new actions inserted"
);
Assert.deepEqual(
PageActions.actionsInUrlbar.slice(
PageActions.actionsInUrlbar.length - (actions.length + 1)
PageActions.actionsInUrlbar(window).slice(
PageActions.actionsInUrlbar(window).length - (actions.length + 1)
).map(a => a.id),
actions.map(a => a.id).concat([PageActions.ACTION_ID_BOOKMARK]),
"PageActions.actionsInUrlbar has new actions inserted"
@ -934,9 +953,9 @@ add_task(async function migrate1() {
Assert.equal(PageActions._persistedActions.version, 1, "Correct version");
// Need to set copyURL's _shownInUrlbar. It won't be set since it's false by
// Need to set copyURL's _pinnedToUrlbar. It won't be set since it's false by
// default and we reached directly into persisted storage above.
PageActions.actionForID("copyURL")._shownInUrlbar = true;
PageActions.actionForID("copyURL")._pinnedToUrlbar = true;
// expected order
let orderedIDs = [
@ -952,7 +971,7 @@ add_task(async function migrate1() {
"PageActions._persistedActions.idsInUrlbar has right order"
);
Assert.deepEqual(
PageActions.actionsInUrlbar.map(a => a.id),
PageActions.actionsInUrlbar(window).map(a => a.id),
orderedIDs,
"PageActions.actionsInUrlbar has right order"
);
@ -982,7 +1001,7 @@ add_task(async function migrate1() {
// Done, clean up.
await BrowserTestUtils.closeWindow(win);
Services.prefs.clearUserPref(PageActions.PREF_PERSISTED_ACTIONS);
PageActions.actionForID("copyURL").shownInUrlbar = false;
PageActions.actionForID("copyURL").pinnedToUrlbar = false;
});
@ -993,10 +1012,12 @@ add_task(async function perWindowState() {
let action = PageActions.addAction(new PageActions.Action({
iconURL: "chrome://browser/skin/mail.svg",
id: "test-perWindowState",
shownInUrlbar: true,
pinnedToUrlbar: true,
title,
}));
let actionsInUrlbar = PageActions.actionsInUrlbar(window);
// Open a new browser window and load an actionable page so that the action
// shows up in it.
let newWindow = await BrowserTestUtils.openNewBrowserWindow();
@ -1043,6 +1064,53 @@ add_task(async function perWindowState() {
Assert.equal(panelButtonNode2.getAttribute("label"), newPerWinTitle,
"Panel button label in new window");
// Disable the action in the new window.
action.setDisabled(true, newWindow);
Assert.equal(action.getDisabled(), false,
"Disabled: global should remain false");
Assert.equal(action.getDisabled(window), false,
"Disabled: old window should remain false");
Assert.equal(action.getDisabled(newWindow), true,
"Disabled: new window should be true");
// Check PageActions.actionsInUrlbar for each window.
Assert.deepEqual(
PageActions.actionsInUrlbar(window).map(a => a.id),
actionsInUrlbar.map(a => a.id),
"PageActions.actionsInUrlbar: old window should have all actions in urlbar"
);
Assert.deepEqual(
PageActions.actionsInUrlbar(newWindow).map(a => a.id),
actionsInUrlbar.map(a => a.id).filter(id => id != action.id),
"PageActions.actionsInUrlbar: new window should have all actions in urlbar except the test action"
);
// Check the urlbar nodes for the old window.
let actualUrlbarNodeIDs = [];
for (let node = BrowserPageActions.mainButtonNode.nextSibling;
node;
node = node.nextSibling) {
actualUrlbarNodeIDs.push(node.id);
}
Assert.deepEqual(
actualUrlbarNodeIDs,
actionsInUrlbar.map(a => BrowserPageActions.urlbarButtonNodeIDForActionID(a.id)),
"Old window should have all nodes in urlbar"
);
// Check the urlbar nodes for the new window.
actualUrlbarNodeIDs = [];
for (let node = newWindow.BrowserPageActions.mainButtonNode.nextSibling;
node;
node = node.nextSibling) {
actualUrlbarNodeIDs.push(node.id);
}
Assert.deepEqual(
actualUrlbarNodeIDs,
actionsInUrlbar.filter(a => a.id != action.id).map(a => BrowserPageActions.urlbarButtonNodeIDForActionID(a.id)),
"New window should have all nodes in urlbar except for the test action's"
);
// Done, clean up.
await BrowserTestUtils.closeWindow(newWindow);
action.remove();
@ -1056,7 +1124,7 @@ add_task(async function perWindowState() {
// persisted state and retain its last placement in the urlbar.
add_task(async function removeRetainState() {
// Get the list of actions initially in the urlbar.
let initialActionsInUrlbar = PageActions.actionsInUrlbar;
let initialActionsInUrlbar = PageActions.actionsInUrlbar(window);
Assert.ok(initialActionsInUrlbar.length > 0,
"This test expects there to be at least one action in the urlbar initially (like the bookmark star)");
@ -1068,18 +1136,18 @@ add_task(async function removeRetainState() {
}));
// Show its button in the urlbar.
testAction.shownInUrlbar = true;
testAction.pinnedToUrlbar = true;
// "Move" the test action to the front of the urlbar by toggling shownInUrlbar
// for all the other actions in the urlbar.
// "Move" the test action to the front of the urlbar by toggling
// pinnedToUrlbar for all the other actions in the urlbar.
for (let action of initialActionsInUrlbar) {
action.shownInUrlbar = false;
action.shownInUrlbar = true;
action.pinnedToUrlbar = false;
action.pinnedToUrlbar = true;
}
// Check the actions in PageActions.actionsInUrlbar.
Assert.deepEqual(
PageActions.actionsInUrlbar.map(a => a.id),
PageActions.actionsInUrlbar(window).map(a => a.id),
[testAction].concat(initialActionsInUrlbar).map(a => a.id),
"PageActions.actionsInUrlbar should be in expected order: testAction followed by all initial actions"
);
@ -1102,7 +1170,7 @@ add_task(async function removeRetainState() {
// Check the actions in PageActions.actionsInUrlbar.
Assert.deepEqual(
PageActions.actionsInUrlbar.map(a => a.id),
PageActions.actionsInUrlbar(window).map(a => a.id),
initialActionsInUrlbar.map(a => a.id),
"PageActions.actionsInUrlbar should be in expected order after removing test action: all initial actions"
);
@ -1127,11 +1195,11 @@ add_task(async function removeRetainState() {
}));
// Show its button in the urlbar again.
testAction.shownInUrlbar = true;
testAction.pinnedToUrlbar = true;
// Check the actions in PageActions.actionsInUrlbar.
Assert.deepEqual(
PageActions.actionsInUrlbar.map(a => a.id),
PageActions.actionsInUrlbar(window).map(a => a.id),
[testAction].concat(initialActionsInUrlbar).map(a => a.id),
"PageActions.actionsInUrlbar should be in expected order after re-adding test action: testAction followed by all initial actions"
);
@ -1154,6 +1222,200 @@ add_task(async function removeRetainState() {
});
// Opens the context menu on a non-built-in action. (The context menu for
// built-in actions is tested in browser_page_action_menu.js.)
add_task(async function contextMenu() {
// Add a test action.
let action = PageActions.addAction(new PageActions.Action({
id: "test-contextMenu",
title: "Test contextMenu",
pinnedToUrlbar: true,
}));
// Open the panel and then open the context menu on the action's item.
await promisePageActionPanelOpen();
let panelButton = BrowserPageActions.panelButtonNodeForActionID(action.id);
let contextMenuPromise = promisePanelShown("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(panelButton, {
type: "contextmenu",
button: 2,
});
await contextMenuPromise;
// The context menu should show the "don't show" item and the "manage" item.
// Click the "don't show" item.
let menuItems = collectContextMenuItems();
Assert.equal(menuItems.length, 3,
"Context menu has 3 children");
Assert.equal(menuItems[0].label, "Don\u2019t Show in Address Bar",
"Context menu is in the 'don't show' state");
Assert.equal(menuItems[1].localName, "menuseparator",
"menuseparator is present");
Assert.equal(menuItems[2].label, "Manage Extension\u2026",
"'Manage' item is present");
contextMenuPromise = promisePanelHidden("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
await contextMenuPromise;
// The action should be removed from the urlbar.
await BrowserTestUtils.waitForCondition(() => {
return !BrowserPageActions.urlbarButtonNodeForActionID(action.id);
}, "Waiting for urlbar button to be removed");
// Open the context menu again on the action's button in the panel. (The
// panel should still be open.)
contextMenuPromise = promisePanelShown("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(panelButton, {
type: "contextmenu",
button: 2,
});
await contextMenuPromise;
// The context menu should show the "show" item and the "manage" item. Click
// the "show" item.
menuItems = collectContextMenuItems();
Assert.equal(menuItems.length, 3,
"Context menu has 3 children");
Assert.equal(menuItems[0].label, "Show in Address Bar",
"Context menu is in the 'show' state");
Assert.equal(menuItems[1].localName, "menuseparator",
"menuseparator is present");
Assert.equal(menuItems[2].label, "Manage Extension\u2026",
"'Manage' item is present");
contextMenuPromise = promisePanelHidden("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
await contextMenuPromise;
// The action should be added back to the urlbar.
await BrowserTestUtils.waitForCondition(() => {
return BrowserPageActions.urlbarButtonNodeForActionID(action.id);
}, "Waiting for urlbar button to be added back");
// Open the context menu again on the action's button in the panel. (The
// panel should still be open.)
contextMenuPromise = promisePanelShown("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(panelButton, {
type: "contextmenu",
button: 2,
});
await contextMenuPromise;
// The context menu should show the "don't show" item and the "manage" item.
// Click the "manage" item. about:addons should open.
menuItems = collectContextMenuItems();
Assert.equal(menuItems.length, 3,
"Context menu has 3 children");
Assert.equal(menuItems[0].label, "Don\u2019t Show in Address Bar",
"Context menu is in the 'don't show' state");
Assert.equal(menuItems[1].localName, "menuseparator",
"menuseparator is present");
Assert.equal(menuItems[2].label, "Manage Extension\u2026",
"'Manage' item is present");
contextMenuPromise = promisePanelHidden("pageActionContextMenu");
let aboutAddonsPromise =
BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
EventUtils.synthesizeMouseAtCenter(menuItems[2], {});
let values = await Promise.all([aboutAddonsPromise, contextMenuPromise]);
let aboutAddonsTab = values[0];
await BrowserTestUtils.removeTab(aboutAddonsTab);
// Open the context menu on the action's urlbar button.
let urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID(action.id);
contextMenuPromise = promisePanelShown("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(urlbarButton, {
type: "contextmenu",
button: 2,
});
await contextMenuPromise;
// The context menu should show the "don't show" item and the "manage" item.
// Click the "don't show" item.
menuItems = collectContextMenuItems();
Assert.equal(menuItems.length, 3,
"Context menu has 3 children");
Assert.equal(menuItems[0].label, "Don\u2019t Show in Address Bar",
"Context menu is in the 'don't show' state");
Assert.equal(menuItems[1].localName, "menuseparator",
"menuseparator is present");
Assert.equal(menuItems[2].label, "Manage Extension\u2026",
"'Manage' item is present");
contextMenuPromise = promisePanelHidden("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
await contextMenuPromise;
// The action should be removed from the urlbar.
await BrowserTestUtils.waitForCondition(() => {
return !BrowserPageActions.urlbarButtonNodeForActionID(action.id);
}, "Waiting for urlbar button to be removed");
// Open the panel and then open the context menu on the action's item.
await promisePageActionPanelOpen();
contextMenuPromise = promisePanelShown("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(panelButton, {
type: "contextmenu",
button: 2,
});
await contextMenuPromise;
// The context menu should show the "show" item and the "manage" item. Click
// the "show" item.
menuItems = collectContextMenuItems();
Assert.equal(menuItems.length, 3,
"Context menu has 3 children");
Assert.equal(menuItems[0].label, "Show in Address Bar",
"Context menu is in the 'show' state");
Assert.equal(menuItems[1].localName, "menuseparator",
"menuseparator is present");
Assert.equal(menuItems[2].label, "Manage Extension\u2026",
"'Manage' item is present");
contextMenuPromise = promisePanelHidden("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(menuItems[0], {});
await contextMenuPromise;
// The action should be added back to the urlbar.
await BrowserTestUtils.waitForCondition(() => {
return BrowserPageActions.urlbarButtonNodeForActionID(action.id);
}, "Waiting for urlbar button to be added back");
// Open the context menu on the action's urlbar button.
urlbarButton = BrowserPageActions.urlbarButtonNodeForActionID(action.id);
contextMenuPromise = promisePanelShown("pageActionContextMenu");
EventUtils.synthesizeMouseAtCenter(urlbarButton, {
type: "contextmenu",
button: 2,
});
await contextMenuPromise;
// The context menu should show the "don't show" item and the "manage" item.
// Click the "manage" item. about:addons should open.
menuItems = collectContextMenuItems();
Assert.equal(menuItems.length, 3,
"Context menu has 3 children");
Assert.equal(menuItems[0].label, "Don\u2019t Show in Address Bar",
"Context menu is in the 'don't show' state");
Assert.equal(menuItems[1].localName, "menuseparator",
"menuseparator is present");
Assert.equal(menuItems[2].label, "Manage Extension\u2026",
"'Manage' item is present");
contextMenuPromise = promisePanelHidden("pageActionContextMenu");
aboutAddonsPromise =
BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
EventUtils.synthesizeMouseAtCenter(menuItems[2], {});
values = await Promise.all([aboutAddonsPromise, contextMenuPromise]);
aboutAddonsTab = values[0];
await BrowserTestUtils.removeTab(aboutAddonsTab);
// Done, clean up.
action.remove();
// urlbar tests that run after this one can break if the mouse is left over
// the area where the urlbar popup appears, which seems to happen due to the
// above synthesized mouse events. Move it over the urlbar.
EventUtils.synthesizeMouseAtCenter(gURLBar, { type: "mousemove" });
gURLBar.focus();
});
function promisePageActionPanelOpen() {
let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
@ -1235,3 +1497,10 @@ function promisePageActionViewChildrenVisible(panelViewNode) {
return false;
});
}
function collectContextMenuItems() {
let contextMenu = document.getElementById("pageActionContextMenu");
return Array.filter(contextMenu.childNodes, node => {
return window.getComputedStyle(node).visibility == "visible";
});
}

Просмотреть файл

@ -40,6 +40,24 @@ if CONFIG['MOZ_ARTIFACT_BUILDS']:
'../build/prebuilt-interfaces.manifest',
]
# These defines are read in firefox.js
DEFINES['APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
for cdm in CONFIG['MOZ_EME_MODULES']:
DEFINES['MOZ_%s_EME' % cdm.upper()] = True
if CONFIG['MOZ_GPSD']:
DEFINES['MOZ_GPSD'] = True
# These files are specified in this moz.build to pick up DIST_SUBDIR as set in
# this directory, which is un-set in browser/app.
JS_PREFERENCE_PP_FILES += [
'app/profile/firefox.js',
]
FINAL_TARGET_FILES += ['app/blocklist.xml']
FINAL_TARGET_FILES.defaults += ['app/permissions']
with Files("**"):
BUG_COMPONENT = ("Firefox", "General")

Просмотреть файл

@ -664,6 +664,7 @@ button > hbox > label {
.help-label {
margin: 0 4px;
line-height: 22px;
-moz-user-select: none;
}

Просмотреть файл

@ -68,8 +68,6 @@
skin/classic/browser/notification-icons/persistent-storage-blocked.svg (../shared/notification-icons/persistent-storage-blocked.svg)
skin/classic/browser/notification-icons/persistent-storage.svg (../shared/notification-icons/persistent-storage.svg)
skin/classic/browser/notification-icons/plugin-badge.svg (../shared/notification-icons/plugin-badge.svg)
skin/classic/browser/notification-icons/plugin-blocked.svg (../shared/notification-icons/plugin-blocked.svg)
skin/classic/browser/notification-icons/plugin.svg (../shared/notification-icons/plugin.svg)
skin/classic/browser/notification-icons/popup.svg (../shared/notification-icons/popup.svg)
skin/classic/browser/notification-icons/popup-subitem.svg (../shared/notification-icons/popup-subitem.svg)
skin/classic/browser/notification-icons/screen-blocked.svg (../shared/notification-icons/screen-blocked.svg)

Просмотреть файл

@ -223,12 +223,12 @@ html|*#webRTC-previewVideo {
/* PLUGINS */
.plugin-icon {
list-style-image: url(chrome://browser/skin/notification-icons/plugin.svg);
list-style-image: url(chrome://mozapps/skin/plugins/plugin.svg);
transition: fill 1.5s;
}
.plugin-blocked-icon {
list-style-image: url(chrome://browser/skin/notification-icons/plugin-blocked.svg);
list-style-image: url(chrome://mozapps/skin/plugins/plugin-blocked.svg);
transition: fill 1.5s;
}
@ -260,7 +260,7 @@ html|*#webRTC-previewVideo {
}
.plugin-blocked > .plugin-icon {
list-style-image: url(chrome://browser/skin/notification-icons/plugin-blocked.svg);
list-style-image: url(chrome://mozapps/skin/plugins/plugin-blocked.svg);
fill: #d92215 !important;
}

Просмотреть файл

@ -1,5 +1,5 @@
.messageImage[value="plugin-hidden"] {
list-style-image: url(chrome://browser/skin/notification-icons/plugin.svg);
list-style-image: url(chrome://mozapps/skin/plugins/plugin.svg);
}
/* Keep any changes to this style in sync with pluginProblem.css */
@ -10,5 +10,5 @@ notification.pluginVulnerable {
}
notification.pluginVulnerable .messageImage {
list-style-image: url(chrome://browser/skin/notification-icons/plugin-blocked.svg);
list-style-image: url(chrome://mozapps/skin/plugins/plugin-blocked.svg);
}

Просмотреть файл

@ -15,6 +15,10 @@ if CONFIG['OS_ARCH'] == 'WINNT':
else:
DIRS += ['unix']
CRAMTEST_MANIFESTS += [
'tests/cram/cram.ini',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
DIRS += ['annotationProcessors']

Просмотреть файл

@ -6,7 +6,7 @@
# PGO
# ==============================================================
option(env='MOZ_PGO', help='Build with profile guided optimizations')
js_option(env='MOZ_PGO', help='Build with profile guided optimizations')
set_config('MOZ_PGO', depends('MOZ_PGO')(lambda x: bool(x)))
add_old_configure_assignment('MOZ_PGO', depends('MOZ_PGO')(lambda x: bool(x)))

Просмотреть файл

@ -0,0 +1 @@
[test_configure_help.t]

Просмотреть файл

@ -0,0 +1,14 @@
configure --help works
$ cd $TESTDIR/../../..
$ touch $TMP/mozconfig
$ export MOZCONFIG=$TMP/mozconfig
$ ./configure --help 2>& 1 | head -n 7
Adding configure options from */tmp/mozconfig (glob)
checking for vcs source checkout... hg
checking for vcs source checkout... hg
Usage: configure.py [options]
Options: [defaults in brackets after descriptions]
--help print this message

Просмотреть файл

@ -56,8 +56,8 @@ function testScriptSrc(aCallback) {
/** <img src=""> tests **/
var img_global = "chrome://global/skin/media/error.png";
var img_mozapps = "chrome://mozapps/skin/plugins/contentPluginClose.png";
var res_mozapps = "resource://gre/chrome/toolkit/skin/classic/mozapps/plugins/contentPluginClose.png";
var img_mozapps = "chrome://mozapps/skin/plugins/contentPluginCrashed.png";
var res_mozapps = "resource://gre/chrome/toolkit/skin/classic/mozapps/plugins/contentPluginCrashed.png";
var imgTests = [[img_global, "success"],
[img_mozapps, "fail"],

Просмотреть файл

@ -119,6 +119,11 @@ OBJDIR_TARGETS = install export libs clean realclean distclean upload sdk instal
# The default rule is build
build::
ifndef MACH
$(error client.mk must be used via `mach`. Try running \
`./mach $(firstword $(MAKECMDGOALS) $(.DEFAULT_GOAL))`)
endif
# Include baseconfig.mk for its $(MAKE) validation.
include $(TOPSRCDIR)/config/baseconfig.mk

Просмотреть файл

@ -32,53 +32,6 @@ endif
include $(topsrcdir)/config/rules.mk
ifdef WRAP_SYSTEM_INCLUDES
export-preqs = \
$(call mkdir_deps,system_wrappers) \
$(NULL)
export:: $(export-preqs)
$(PYTHON) -m mozbuild.action.preprocessor $(DEFINES) $(ACDEFINES) \
-DMOZ_TREE_CAIRO=$(MOZ_TREE_CAIRO) \
-DMOZ_TREE_PIXMAN=$(MOZ_TREE_PIXMAN) \
-DMOZ_SYSTEM_HUNSPELL=$(MOZ_SYSTEM_HUNSPELL) \
-DMOZ_SYSTEM_BZ2=$(MOZ_SYSTEM_BZ2) \
-DMOZ_SYSTEM_ZLIB=$(MOZ_SYSTEM_ZLIB) \
-DMOZ_SYSTEM_PNG=$(MOZ_SYSTEM_PNG) \
-DMOZ_SYSTEM_JPEG=$(MOZ_SYSTEM_JPEG) \
-DMOZ_SYSTEM_LIBEVENT=$(MOZ_SYSTEM_LIBEVENT) \
-DMOZ_SYSTEM_LIBVPX=$(MOZ_SYSTEM_LIBVPX) \
-DMOZ_SYSTEM_ICU=$(MOZ_SYSTEM_ICU) \
$(srcdir)/system-headers $(srcdir)/stl-headers | $(PERL) $(topsrcdir)/nsprpub/config/make-system-wrappers.pl system_wrappers
$(INSTALL) system_wrappers $(DIST)
GARBAGE_DIRS += system_wrappers
endif
ifdef WRAP_STL_INCLUDES
ifdef GNU_CXX
stl_compiler = gcc
else
ifdef _MSC_VER
stl_compiler = msvc
endif
endif
endif
ifdef stl_compiler
STL_WRAPPERS_SENTINEL = $(DIST)/stl_wrappers/sentinel
$(STL_WRAPPERS_SENTINEL): $(srcdir)/make-stl-wrappers.py $(srcdir)/$(stl_compiler)-stl-wrapper.template.h $(srcdir)/stl-headers $(GLOBAL_DEPS)
$(PYTHON) $(srcdir)/make-stl-wrappers.py stl_wrappers $(stl_compiler) $(srcdir)/$(stl_compiler)-stl-wrapper.template.h $(srcdir)/stl-headers
$(PYTHON) $(srcdir)/nsinstall.py -t stl_wrappers $(DIST)
touch $(STL_WRAPPERS_SENTINEL)
export:: $(STL_WRAPPERS_SENTINEL)
GARBAGE += $(STL_WRAPPERS_SENTINEL)
GARBAGE_DIRS += stl_wrappers
endif
GARBAGE += \
$(FINAL_LINK_COMPS) $(FINAL_LINK_LIBS) $(FINAL_LINK_COMP_NAMES) $(srcdir)/*.pyc *.pyc

Просмотреть файл

@ -22,31 +22,13 @@ def header_path(header, compiler):
# hope someone notices this ...
raise NotImplementedError(compiler)
def is_comment(line):
return re.match(r'\s*#.*', line)
def main(outdir, compiler, template_file, header_list_file):
if not os.path.isdir(outdir):
os.mkdir(outdir)
# The 'unused' arg is the output file from the file_generate action. We actually
# generate all the files in header_list
def gen_wrappers(unused, outdir, compiler, template_file, *header_list):
template = open(template_file, 'r').read()
for header in open(header_list_file, 'r'):
header = header.rstrip()
if 0 == len(header) or is_comment(header):
continue
for header in header_list:
path = header_path(header, compiler)
with FileAvoidWrite(os.path.join(outdir, header)) as f:
f.write(string.Template(template).substitute(HEADER=header,
HEADER_PATH=path))
if __name__ == '__main__':
if 5 != len(sys.argv):
print("""Usage:
python {0} OUT_DIR ('msvc'|'gcc') TEMPLATE_FILE HEADER_LIST_FILE
""".format(sys.argv[0]), file=sys.stderr)
sys.exit(1)
main(*sys.argv[1:])

Просмотреть файл

@ -0,0 +1,23 @@
# 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/.
from __future__ import print_function
import os
import sys
from mozbuild.util import FileAvoidWrite
header_template = '''#pragma GCC system_header
#pragma GCC visibility push(default)
#include_next <%(header)s>
#pragma GCC visibility pop
'''
# The 'unused' arg is the output file from the file_generate action. We actually
# generate all the files in header_list
def gen_wrappers(unused, outdir, *header_list):
for header in header_list:
with FileAvoidWrite(os.path.join(outdir, header)) as f:
f.write(header_template % {
'header': header,
})

Просмотреть файл

@ -42,3 +42,35 @@ HOST_DEFINES = {
'UNICODE': True,
'_UNICODE': True,
}
include('stl-headers.mozbuild')
if CONFIG['WRAP_STL_INCLUDES']:
stl_compiler = None
if CONFIG['GNU_CXX']:
stl_compiler = 'gcc'
elif CONFIG['_MSC_VER']:
stl_compiler = 'msvc'
if stl_compiler:
template_file = SRCDIR + '/%s-stl-wrapper.template.h' % stl_compiler
output_dir = '../dist/stl_wrappers'
# We have to use a sentinel file as the first file because the
# file_generate action will create it for us, but we want to create all
# the files in gen_wrappers()
outputs = tuple(['stl.sentinel'] + ['%s/%s' % (output_dir, h) for h in stl_headers])
GENERATED_FILES += [outputs]
stl = GENERATED_FILES[outputs]
stl.script = 'make-stl-wrappers.py:gen_wrappers'
stl.flags = [output_dir, stl_compiler, template_file]
stl.flags.extend(stl_headers)
if CONFIG['WRAP_SYSTEM_INCLUDES']:
include('system-headers.mozbuild')
output_dir = '../dist/system_wrappers'
outputs = tuple(['system-header.sentinel'] + ['%s/%s' % (output_dir, h) for h in stl_headers + system_headers])
GENERATED_FILES += [outputs]
system = GENERATED_FILES[outputs]
system.script = 'make-system-wrappers.py:gen_wrappers'
system.flags = [output_dir]
system.flags.extend(stl_headers)
system.flags.extend(system_headers)

Просмотреть файл

@ -1,52 +0,0 @@
#
# This file contains a list the of STL headers that have been reviewed
# for exception safety and approved. See
#
# https://bugzilla.mozilla.org/show_bug.cgi?id=551254
#
# At build time, each header listed here is converted into a "wrapper
# header" that is installed into dist/stl_includes.
#
# If you would like to request a new STL header <foo> be added, please
# file a Core:XPCOM bug with a title like "STL: Review exception
# safety of <foo> for gcc and MSVC".
#
new
# FIXME: these headers haven't been reviewed yet, but we use them
# unsafely in Gecko, so we might as well prevent them from
# throwing exceptions
algorithm
atomic
deque
functional
ios
iosfwd
iostream
istream
iterator
limits
list
map
memory
ostream
set
stack
string
thread
type_traits
unordered_map
unordered_set
utility
vector
cassert
climits
cmath
cstdarg
cstdio
cstdlib
cstring
cwchar
tuple
xutility

Просмотреть файл

@ -0,0 +1,57 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
# This list contains the of STL headers that have been reviewed for exception
# safety and approved. See
#
# https://bugzilla.mozilla.org/show_bug.cgi?id=551254
#
# At build time, each header listed here is converted into a "wrapper
# header" that is installed into dist/stl_includes.
#
# If you would like to request a new STL header <foo> be added, please
# file a Core:XPCOM bug with a title like "STL: Review exception
# safety of <foo> for gcc and MSVC".
stl_headers = [
'new',
# FIXME: these headers haven't been reviewed yet, but we use them
# unsafely in Gecko, so we might as well prevent them from
# throwing exceptions
'algorithm',
'atomic',
'deque',
'functional',
'ios',
'iosfwd',
'iostream',
'istream',
'iterator',
'limits',
'list',
'map',
'memory',
'ostream',
'set',
'stack',
'string',
'thread',
'type_traits',
'unordered_map',
'unordered_set',
'utility',
'vector',
'cassert',
'climits',
'cmath',
'cstdarg',
'cstdio',
'cstdlib',
'cstring',
'cwchar',
'tuple',
'xutility',
]

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -62,6 +62,55 @@ add_task(async function () {
await assertRowSelected(numRows - 1);
});
add_task(async function () {
info("Test 3 JSON row selection started");
// Create a JSON with a row taller than the panel.
let json = JSON.stringify([0, "a ".repeat(1e4), 1]);
await addJsonViewTab("data:application/json," + encodeURI(json));
is(await getElementCount(".treeRow"), 3, "Got the expected number of rows.");
await assertRowSelected(null);
await evalInContent("var scroller = $('.jsonPanelBox .panelContent')");
await evalInContent("var row = $('.treeRow:nth-child(2)')");
ok(await evalInContent("scroller.clientHeight < row.clientHeight"),
"The row is taller than the scroller.");
is(await evalInContent("scroller.scrollTop"), 0, "Initially scrolled to the top.");
// Select the tall row.
await evalInContent("row.click()");
await assertRowSelected(2);
is(await evalInContent("scroller.scrollTop"), await evalInContent("row.offsetTop"),
"Scrolled to the top of the row.");
// Select the last row.
await evalInContent("$('.treeRow:last-child').click()");
await assertRowSelected(3);
is(await evalInContent("scroller.scrollTop + scroller.offsetHeight"),
await evalInContent("scroller.scrollHeight"), "Scrolled to the bottom.");
// Select the tall row.
await evalInContent("row.click()");
await assertRowSelected(2);
is(await evalInContent("scroller.scrollTop + scroller.offsetHeight"),
await evalInContent("row.offsetTop + row.offsetHeight"),
"Scrolled to the bottom of the row.");
// Scroll up a bit, so that both the top and bottom of the row are not visible.
let scroll = await evalInContent(
"scroller.scrollTop = Math.ceil((scroller.scrollTop + row.offsetTop) / 2)");
ok(await evalInContent("scroller.scrollTop > row.offsetTop"),
"The top of the row is not visible.");
ok(await evalInContent("scroller.scrollTop + scroller.offsetHeight")
< await evalInContent("row.offsetTop + row.offsetHeight"),
"The bottom of the row is not visible.");
// Select the tall row.
await evalInContent("row.click()");
await assertRowSelected(2);
is(await evalInContent("scroller.scrollTop"), scroll, "Scroll did not change");
});
async function assertRowSelected(rowNum) {
let idx = evalInContent("[].indexOf.call($$('.treeRow'), $('.treeRow.selected'))");
is(await idx + 1, +rowNum, `${rowNum ? "The row #" + rowNum : "No row"} is selected.`);

Просмотреть файл

@ -321,13 +321,7 @@ charts.totalSize=Size: %S KB
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# This is the label displayed in the performance analysis view for the
# total requests time, in seconds.
charts.totalSeconds=Time: %1$S second;Time: %1$S seconds
# LOCALIZATION NOTE (charts.totalSecondsNonBlocking): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# This is the label displayed in the performance analysis view for the
# total requests time (non-blocking), in seconds.
charts.totalSecondsNonBlocking=Non blocking time: %1$S second;Non blocking time: %1$S seconds
charts.totalSeconds=Time: #1 second;Time: #1 seconds
# LOCALIZATION NOTE (charts.totalCached): This is the label displayed
# in the performance analysis view for total cached responses.
@ -354,11 +348,6 @@ charts.transferred=Transferred
# in the header column in the performance analysis view for time of request.
charts.time=Time
# LOCALIZATION NOTE (charts.nonBlockingTime): This is the label displayed
# in the header column in the performance analysis view for non blocking
# time of request.
charts.nonBlockingTime=Non blocking time
# LOCALIZATION NOTE (netRequest.headers): A label used for Headers tab
# This tab displays list of HTTP headers
netRequest.headers=Headers

Просмотреть файл

@ -132,7 +132,6 @@ class StatisticsPanel extends Component {
size: L10N.getStr("charts.size"),
transferredSize: L10N.getStr("charts.transferred"),
time: L10N.getStr("charts.time"),
nonBlockingTime: L10N.getStr("charts.nonBlockingTime"),
},
data,
strings: {
@ -143,8 +142,6 @@ class StatisticsPanel extends Component {
getSizeWithDecimals(value / 1024)),
time: (value) =>
L10N.getFormatStr("charts.totalS", getTimeWithDecimals(value / 1000)),
nonBlockingTime: (value) =>
L10N.getFormatStr("charts.totalS", getTimeWithDecimals(value / 1000)),
},
totals: {
cached: (total) => L10N.getFormatStr("charts.totalCached", total),
@ -158,13 +155,7 @@ class StatisticsPanel extends Component {
let seconds = total / 1000;
let string = getTimeWithDecimals(seconds);
return PluralForm.get(seconds,
L10N.getFormatStr("charts.totalSeconds", string));
},
nonBlockingTime: (total) => {
let seconds = total / 1000;
let string = getTimeWithDecimals(seconds);
return PluralForm.get(seconds,
L10N.getFormatStr("charts.totalSecondsNonBlocking", string));
L10N.getStr("charts.totalSeconds")).replace("#1", string);
},
},
sorted: true,
@ -194,7 +185,6 @@ class StatisticsPanel extends Component {
size: 0,
transferredSize: 0,
time: 0,
nonBlockingTime: 0,
}));
for (let request of requests) {
@ -234,9 +224,6 @@ class StatisticsPanel extends Component {
data[type].time += request.totalTime || 0;
data[type].size += request.contentSize || 0;
data[type].transferredSize += request.transferredSize || 0;
let nonBlockingTime =
request.eventTimings.totalTime - request.eventTimings.timings.blocked;
data[type].nonBlockingTime += nonBlockingTime || 0;
} else {
data[type].cached++;
}

Просмотреть файл

@ -287,7 +287,19 @@ define(function (require, exports, module) {
this.setState(Object.assign({}, this.state, {
selected: row.id
}));
row.scrollIntoView({block: "nearest"});
// If the top or bottom side of the row is not visible and there is available space
// beyond the opposite one, then attempt to scroll the hidden side into view, but
// without hiding the visible side.
let scroller = scrollContainer(row);
if (!scroller) {
return;
}
let scrollToTop = row.offsetTop;
let scrollToBottom = scrollToTop + row.offsetHeight - scroller.offsetHeight;
let max = Math.max(scrollToTop, scrollToBottom);
let min = Math.min(scrollToTop, scrollToBottom);
scroller.scrollTop = Math.max(min, Math.min(max, scroller.scrollTop));
}
isSelected(nodePath) {
@ -502,6 +514,18 @@ define(function (require, exports, module) {
return typeof value == "string" && value.length > 50;
}
function scrollContainer(element) {
let parent = element.parentElement;
let window = element.ownerDocument.defaultView;
if (!parent || !window) {
return null;
}
if (window.getComputedStyle(parent).overflowY != "visible") {
return parent;
}
return scrollContainer(parent);
}
// Exports from this module
module.exports = TreeView;
});

Просмотреть файл

@ -23,6 +23,12 @@ const MESSAGES = {
SystemMenu: "menu-message",
};
// Google analytics parameters that should be added to all outgoing links.
const GA_PARAMETERS = [
["utm_source", "devtools"],
["utm_medium", "onboarding"],
];
const ABOUTDEVTOOLS_STRINGS = "chrome://devtools-shim/locale/aboutdevtools.properties";
const aboutDevtoolsBundle = Services.strings.createBundle(ABOUTDEVTOOLS_STRINGS);
@ -186,6 +192,14 @@ window.addEventListener("load", function () {
featuresContainer.appendChild(createFeatureEl(feature));
}
// Add Google Analytics parameters to all the external links.
let externalLinks = [...document.querySelectorAll("a.external")];
for (let link of externalLinks) {
let linkUrl = new URL(link.getAttribute("href"));
GA_PARAMETERS.forEach(([key, value]) => linkUrl.searchParams.set(key, value));
link.setAttribute("href", linkUrl.href);
}
// Update the current page based on the current value of DEVTOOLS_ENABLED_PREF.
updatePage();

Просмотреть файл

@ -409,8 +409,54 @@ public:
template<typename A, typename B>
bool operator==(const RangeBoundaryBase<A, B>& aOther) const
{
return mParent == aOther.mParent &&
(mRef ? mRef == aOther.mRef : mOffset == aOther.mOffset);
if (mParent != aOther.mParent) {
return false;
}
if (mOffset.isSome() && aOther.mOffset.isSome()) {
// If both mOffset are set, we need to compare both mRef too because
// the relation of mRef and mOffset have already broken by DOM tree
// changes.
if (mOffset != aOther.mOffset) {
return false;
}
if (mRef == aOther.mRef) {
return true;
}
if (NS_WARN_IF(mRef && aOther.mRef)) {
// In this case, relation between mRef and mOffset of one of or both of
// them doesn't match with current DOM tree since the DOM tree might
// have been changed after computing mRef or mOffset.
return false;
}
// If one of mRef hasn't been computed yet, we should compare them only
// with mOffset. Perhaps, we shouldn't copy mRef from non-nullptr one to
// nullptr one since if we copy it here, it may be unexpected behavior for
// some callers.
return true;
}
if (mOffset.isSome() && !mRef &&
!aOther.mOffset.isSome() && aOther.mRef) {
// If this has only mOffset and the other has only mRef, this needs to
// compute mRef now.
EnsureRef();
return mRef == aOther.mRef;
}
if (!mOffset.isSome() && mRef &&
aOther.mOffset.isSome() && !aOther.mRef) {
// If this has only mRef and the other has only mOffset, the other needs
// to compute mRef now.
aOther.EnsureRef();
return mRef == aOther.mRef;
}
// If mOffset of one of them hasn't been computed from mRef yet, we should
// compare only with mRef. Perhaps, we shouldn't copy mOffset from being
// some one to not being some one since if we copy it here, it may be
// unexpected behavior for some callers.
return mRef == aOther.mRef;
}
template<typename A, typename B>

Просмотреть файл

@ -1737,12 +1737,6 @@ void HTMLMediaElement::ShutdownDecoder()
void HTMLMediaElement::AbortExistingLoads()
{
// If there is no existing decoder then we don't have anything to
// report. This prevents reporting the initial load from an
// empty video element as a failed EME load.
if (mDecoder) {
ReportEMETelemetry();
}
// Abort any already-running instance of the resource selection algorithm.
mLoadWaitStatus = NOT_WAITING;
@ -4590,18 +4584,6 @@ void HTMLMediaElement::HiddenVideoStop()
mVideoDecodeSuspendTimer = nullptr;
}
void
HTMLMediaElement::ReportEMETelemetry()
{
// Report telemetry for EME videos when a page is unloaded.
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (mIsEncrypted && Preferences::GetBool("media.eme.enabled")) {
Telemetry::Accumulate(Telemetry::VIDEO_EME_PLAY_SUCCESS, mLoadedDataFired);
LOG(LogLevel::Debug, ("%p VIDEO_EME_PLAY_SUCCESS = %s",
this, mLoadedDataFired ? "true" : "false"));
}
}
void
HTMLMediaElement::ReportTelemetry()
{
@ -6463,7 +6445,6 @@ void HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement, bool aSuspendE
UpdateAudioChannelPlayingState();
if (aPauseElement) {
ReportTelemetry();
ReportEMETelemetry();
// For EME content, we may force destruction of the CDM client (and CDM
// instance if this is the last client for that CDM instance) and

Просмотреть файл

@ -1241,8 +1241,6 @@ protected:
*/
void HiddenVideoStop();
void ReportEMETelemetry();
void ReportTelemetry();
// Seeks to aTime seconds. aSeekType can be Exact to seek to exactly the

Просмотреть файл

@ -29,9 +29,8 @@ ChannelMediaResource::ChannelMediaResource(MediaResourceCallback* aCallback,
nsIURI* aURI,
bool aIsPrivateBrowsing)
: BaseMediaResource(aCallback, aChannel, aURI)
, mReopenOnError(false)
, mCacheStream(this, aIsPrivateBrowsing)
, mSuspendAgent(mChannel, mCacheStream)
, mSuspendAgent(mCacheStream)
{
}
@ -41,10 +40,9 @@ ChannelMediaResource::ChannelMediaResource(
nsIURI* aURI,
const MediaChannelStatistics& aStatistics)
: BaseMediaResource(aCallback, aChannel, aURI)
, mReopenOnError(false)
, mCacheStream(this, /* aIsPrivateBrowsing = */ false)
, mChannelStatistics(aStatistics)
, mSuspendAgent(mChannel, mCacheStream)
, mSuspendAgent(mCacheStream)
{
}
@ -86,7 +84,7 @@ ChannelMediaResource::Listener::OnStopRequest(nsIRequest* aRequest,
MOZ_ASSERT(NS_IsMainThread());
if (!mResource)
return NS_OK;
return mResource->OnStopRequest(aRequest, aStatus);
return mResource->OnStopRequest(aRequest, aStatus, mReopenOnError);
}
nsresult
@ -296,9 +294,8 @@ ChannelMediaResource::OnStartRequest(nsIRequest* aRequest,
mCacheStream.NotifyDataStarted(mLoadID, startOffset, seekable);
mIsTransportSeekable = seekable;
mChannelStatistics.Start();
mReopenOnError = false;
mSuspendAgent.UpdateSuspendedStatusIfNeeded();
mSuspendAgent.Delegate(mChannel);
// Fires an initial progress event.
owner->DownloadProgressed();
@ -371,7 +368,9 @@ ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
}
nsresult
ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
ChannelMediaResource::OnStopRequest(nsIRequest* aRequest,
nsresult aStatus,
bool aReopenOnError)
{
NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
NS_ASSERTION(!mSuspendAgent.IsSuspended(),
@ -386,7 +385,7 @@ ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
// cases where we don't need to reopen are when *we* closed the stream.
// But don't reopen if we need to seek and we don't think we can... that would
// cause us to just re-read the stream, which would be really bad.
if (mReopenOnError && aStatus != NS_ERROR_PARSED_DATA_CACHED &&
if (aReopenOnError && aStatus != NS_ERROR_PARSED_DATA_CACHED &&
aStatus != NS_BINDING_ABORTED &&
(GetOffset() == 0 || (GetLength() > 0 && GetOffset() != GetLength() &&
mIsTransportSeekable))) {
@ -425,8 +424,9 @@ ChannelMediaResource::OnChannelRedirect(nsIChannel* aOld,
uint32_t aFlags,
int64_t aOffset)
{
// OnChannelRedirect() is followed by OnStartRequest() where we will
// call mSuspendAgent.Delegate().
mChannel = aNew;
mSuspendAgent.NotifyChannelOpened(mChannel);
return SetupChannelHeaders(aOffset);
}
@ -626,7 +626,7 @@ void ChannelMediaResource::CloseChannel()
mChannelStatistics.Stop();
if (mChannel) {
mSuspendAgent.NotifyChannelClosing();
mSuspendAgent.Revoke();
// The status we use here won't be passed to the decoder, since
// we've already revoked the listener. It can however be passed
// to nsDocumentViewer::LoadComplete if our channel is the one
@ -730,7 +730,7 @@ ChannelMediaResource::Resume()
mChannelStatistics.Start();
// if an error occurs after Resume, assume it's because the server
// timed out the connection and we should reopen it.
mReopenOnError = true;
mListener->SetReopenOnError();
element->DownloadResumed();
} else {
int64_t totalLength = GetLength();
@ -818,8 +818,6 @@ ChannelMediaResource::RecreateChannel()
cos->AddClassFlags(nsIClassOfService::DontThrottle);
}
mSuspendAgent.NotifyChannelOpened(mChannel);
// Tell the cache to reset the download status when the channel is reopened.
mCacheStream.NotifyChannelRecreated();
@ -1067,28 +1065,31 @@ ChannelSuspendAgent::Resume()
}
void
ChannelSuspendAgent::UpdateSuspendedStatusIfNeeded()
ChannelSuspendAgent::Delegate(nsIChannel* aChannel)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mIsChannelSuspended && IsSuspended()) {
MOZ_ASSERT(aChannel);
MOZ_ASSERT(!mChannel, "The previous channel not closed.");
MOZ_ASSERT(!mIsChannelSuspended);
mChannel = aChannel;
// Ensure the suspend status of the channel matches our suspend count.
if (IsSuspended()) {
SuspendInternal();
}
}
void
ChannelSuspendAgent::NotifyChannelOpened(nsIChannel* aChannel)
ChannelSuspendAgent::Revoke()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aChannel);
mChannel = aChannel;
}
void
ChannelSuspendAgent::NotifyChannelClosing()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mChannel);
// Before close the channel, it need to be resumed to make sure its internal
if (!mChannel) {
// Channel already revoked. Nothing to do.
return;
}
// Before closing the channel, it needs to be resumed to make sure its internal
// state is correct. Besides, We need to suspend the channel after recreating.
if (mIsChannelSuspended) {
mChannel->Resume();
@ -1104,5 +1105,4 @@ ChannelSuspendAgent::IsSuspended()
return (mSuspendCount > 0);
}
} // mozilla namespace

Просмотреть файл

@ -24,9 +24,8 @@ namespace mozilla {
class ChannelSuspendAgent
{
public:
ChannelSuspendAgent(nsIChannel* aChannel, MediaCacheStream& aCacheStream)
: mChannel(aChannel)
, mCacheStream(aCacheStream)
explicit ChannelSuspendAgent(MediaCacheStream& aCacheStream)
: mCacheStream(aCacheStream)
{
}
@ -40,21 +39,16 @@ public:
// Return true only when the suspend count is equal to zero.
bool Resume();
// Call after opening channel, set channel and check whether the channel
// needs to be suspended.
void NotifyChannelOpened(nsIChannel* aChannel);
// Call before closing channel, reset the channel internal status if needed.
void NotifyChannelClosing();
// Check whether we need to suspend the channel.
void UpdateSuspendedStatusIfNeeded();
// Tell the agent to manage the suspend status of the channel.
void Delegate(nsIChannel* aChannel);
// Stop the management of the suspend status of the channel.
void Revoke();
private:
// Only suspends channel but not changes the suspend count.
void SuspendInternal();
nsIChannel* mChannel;
nsIChannel* mChannel = nullptr;
MediaCacheStream& mCacheStream;
uint32_t mSuspendCount = 0;
bool mIsChannelSuspended = false;
@ -181,6 +175,7 @@ public:
NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
void Revoke();
void SetReopenOnError() { mReopenOnError = true; }
private:
Mutex mMutex;
@ -188,6 +183,10 @@ public:
// So it can be read without lock on the main thread or on other threads
// with the lock.
RefPtr<ChannelMediaResource> mResource;
// When this flag is set, if we get a network error we should silently
// reopen the stream. Main thread only.
bool mReopenOnError = false;
const int64_t mOffset;
const uint32_t mLoadID;
};
@ -201,7 +200,9 @@ protected:
bool IsSuspendedByCache();
// These are called on the main thread by Listener.
nsresult OnStartRequest(nsIRequest* aRequest, int64_t aRequestOffset);
nsresult OnStopRequest(nsIRequest* aRequest, nsresult aStatus);
nsresult OnStopRequest(nsIRequest* aRequest,
nsresult aStatus,
bool aReopenOnError);
nsresult OnDataAvailable(uint32_t aLoadID,
nsIInputStream* aStream,
uint32_t aCount);
@ -255,9 +256,6 @@ protected:
// Used by the cache to store the offset to seek to when we are resumed.
// -1 means no seek initiated by the cache is waiting.
int64_t mPendingSeekOffset = -1;
// When this flag is set, if we get a network error we should silently
// reopen the stream.
bool mReopenOnError;
// Any thread access
MediaCacheStream mCacheStream;

Просмотреть файл

@ -2517,8 +2517,6 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
ReentrantMonitorAutoEnter mon(mMediaCache->GetReentrantMonitor());
if (mClosed)
return NS_ERROR_FAILURE;
// Cache the offset in case it is changed again when we are waiting for the
// monitor to be notified to avoid reading at the wrong position.
@ -2527,10 +2525,16 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
uint32_t count = 0;
// Read one block (or part of a block) at a time
while (count < aCount) {
if (mClosed) {
return NS_ERROR_ABORT;
}
int32_t streamBlock = OffsetToBlockIndex(streamOffset);
if (streamBlock < 0) {
break;
LOGE("Stream %p invalid offset=%" PRId64, this, streamOffset);
return NS_ERROR_ILLEGAL_VALUE;
}
uint32_t offsetInStreamBlock = uint32_t(streamOffset - streamBlock*BLOCK_SIZE);
int64_t size = std::min<int64_t>(aCount - count, BLOCK_SIZE - offsetInStreamBlock);
@ -2551,12 +2555,6 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
if (cacheBlock < 0) {
// We don't have a complete cached block here.
if (count > 0) {
// Some data has been read, so return what we've got instead of
// blocking or trying to find a stream with a partial block.
break;
}
// See if the data is available in the partial cache block of any
// stream reading this resource. We need to do this in case there is
// another stream with this resource that has all the data to the end of
@ -2566,7 +2564,8 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
while (MediaCacheStream* stream = iter.Next()) {
if (OffsetToBlockIndexUnchecked(stream->mChannelOffset) ==
streamBlock &&
streamOffset < stream->mChannelOffset) {
streamOffset < stream->mChannelOffset &&
stream->mChannelOffset == stream->mStreamLength) {
streamWithPartialBlock = stream;
break;
}
@ -2579,13 +2578,16 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
// Clamp bytes until 64-bit file size issues are fixed.
bytes = std::min(bytes, int64_t(INT32_MAX));
MOZ_ASSERT(bytes >= 0 && bytes <= aCount, "Bytes out of range.");
memcpy(aBuffer,
streamWithPartialBlock->mPartialBlockBuffer.get() + offsetInStreamBlock, bytes);
memcpy(aBuffer + count,
streamWithPartialBlock->mPartialBlockBuffer.get() +
offsetInStreamBlock,
bytes);
if (mCurrentMode == MODE_METADATA) {
streamWithPartialBlock->mMetadataInPartialBlockBuffer = true;
}
streamOffset += bytes;
count = bytes;
count += bytes;
// Break for we've reached EOS and have nothing more to read.
break;
}
@ -2598,11 +2600,6 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
// No data has been read yet, so block
mon.Wait();
if (mClosed) {
// We may have successfully read some data, but let's just throw
// that out.
return NS_ERROR_FAILURE;
}
continue;
}
@ -2615,22 +2612,25 @@ MediaCacheStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
nsresult rv = mMediaCache->ReadCacheFile(
offset, aBuffer + count, int32_t(size), &bytes);
if (NS_FAILED(rv)) {
if (count == 0)
return rv;
// If we did successfully read some data, may as well return it
break;
nsCString name;
GetErrorName(rv, name);
LOGE("Stream %p ReadCacheFile failed, rv=%s", this, name.Data());
return rv;
}
streamOffset += bytes;
count += bytes;
}
if (count > 0) {
// Some data was read, so queue an update since block priorities may
// have changed
mMediaCache->QueueUpdate();
}
LOG("Stream %p Read at %" PRId64 " count=%d", this, streamOffset-count, count);
*aBytes = count;
if (count == 0) {
return NS_OK;
}
// Some data was read, so queue an update since block priorities may
// have changed
mMediaCache->QueueUpdate();
LOG("Stream %p Read at %" PRId64 " count=%d", this, streamOffset-count, count);
mStreamOffset = streamOffset;
return NS_OK;
}

Просмотреть файл

@ -414,34 +414,14 @@ MediaResourceIndex::UncachedReadAt(int64_t aOffset,
uint32_t aCount,
uint32_t* aBytes) const
{
*aBytes = 0;
if (aOffset < 0) {
return NS_ERROR_ILLEGAL_VALUE;
}
if (aCount != 0) {
for (;;) {
uint32_t bytesRead = 0;
nsresult rv = mResource->ReadAt(aOffset, aBuffer, aCount, &bytesRead);
if (NS_FAILED(rv)) {
return rv;
}
if (bytesRead == 0) {
break;
}
*aBytes += bytesRead;
aCount -= bytesRead;
if (aCount == 0) {
break;
}
aOffset += bytesRead;
if (aOffset < 0) {
// Very unlikely overflow.
return NS_ERROR_FAILURE;
}
aBuffer += bytesRead;
}
if (aCount == 0) {
*aBytes = 0;
return NS_OK;
}
return NS_OK;
return mResource->ReadAt(aOffset, aBuffer, aCount, aBytes);
}
nsresult
@ -451,36 +431,15 @@ MediaResourceIndex::UncachedRangedReadAt(int64_t aOffset,
uint32_t aExtraCount,
uint32_t* aBytes) const
{
*aBytes = 0;
uint32_t count = aRequestedCount + aExtraCount;
if (aOffset < 0 || count < aRequestedCount) {
return NS_ERROR_ILLEGAL_VALUE;
}
if (count != 0) {
for (;;) {
uint32_t bytesRead = 0;
nsresult rv = mResource->ReadAt(aOffset, aBuffer, count, &bytesRead);
if (NS_FAILED(rv)) {
return rv;
}
if (bytesRead == 0) {
break;
}
*aBytes += bytesRead;
count -= bytesRead;
if (count <= aExtraCount) {
// We have read at least aRequestedCount, don't loop anymore.
break;
}
aOffset += bytesRead;
if (aOffset < 0) {
// Very unlikely overflow.
return NS_ERROR_FAILURE;
}
aBuffer += bytesRead;
}
if (count == 0) {
*aBytes = 0;
return NS_OK;
}
return NS_OK;
return mResource->ReadAt(aOffset, aBuffer, count, aBytes);
}
nsresult
@ -516,30 +475,17 @@ MediaResourceIndex::Seek(int32_t aWhence, int64_t aOffset)
already_AddRefed<MediaByteBuffer>
MediaResourceIndex::MediaReadAt(int64_t aOffset, uint32_t aCount) const
{
NS_ENSURE_TRUE(aOffset >= 0, nullptr);
RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
if (aOffset < 0) {
return bytes.forget();
}
bool ok = bytes->SetLength(aCount, fallible);
NS_ENSURE_TRUE(ok, nullptr);
char* curr = reinterpret_cast<char*>(bytes->Elements());
const char* start = curr;
while (aCount > 0) {
uint32_t bytesRead;
nsresult rv = mResource->ReadAt(aOffset, curr, aCount, &bytesRead);
NS_ENSURE_SUCCESS(rv, nullptr);
if (!bytesRead) {
break;
}
aOffset += bytesRead;
if (aOffset < 0) {
// Very unlikely overflow.
break;
}
aCount -= bytesRead;
curr += bytesRead;
}
bytes->SetLength(curr - start);
uint32_t bytesRead = 0;
nsresult rv = mResource->ReadAt(
aOffset, reinterpret_cast<char*>(bytes->Elements()), aCount, &bytesRead);
NS_ENSURE_SUCCESS(rv, nullptr);
bytes->SetLength(bytesRead);
return bytes.forget();
}

Просмотреть файл

@ -268,10 +268,10 @@ GeckoMediaPluginService::GetCDM(const NodeId& aNodeId,
}
holder->Resolve(cdm, __func__);
},
[rawHolder] {
[rawHolder](MediaResult result) {
nsPrintfCString reason(
"%s::%s failed since GetContentParent rejects the promise.",
__CLASS__, __FUNCTION__);
"%s::%s failed since GetContentParent rejects the promise with reason %s.",
__CLASS__, __FUNCTION__, result.Description().get());
UniquePtr<PromiseHolder> holder(rawHolder);
holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()), __func__);
});

Просмотреть файл

@ -53,7 +53,7 @@ struct NodeId
};
typedef MozPromise<RefPtr<GMPContentParent::CloseBlocker>,
nsresult,
MediaResult,
/* IsExclusive = */ true>
GetGMPContentParentPromise;
typedef MozPromise<RefPtr<ChromiumCDMParent>,

Просмотреть файл

@ -84,6 +84,8 @@ GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
nsCString displayName;
uint32_t pluginId = 0;
ipc::Endpoint<PGMPContentParent> endpoint;
nsCString errorDescription = NS_LITERAL_CSTRING("");
bool ok = child->SendLaunchGMP(nodeIdString,
api,
tags,
@ -92,7 +94,8 @@ GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
&otherProcess,
&displayName,
&endpoint,
&rv);
&rv,
&errorDescription);
if (helper && pluginId) {
// Note: Even if the launch failed, we need to connect the crash
// helper so that if the launch failed due to the plugin crashing,
@ -103,10 +106,14 @@ GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
}
if (!ok || NS_FAILED(rv)) {
LOGD(("GeckoMediaPluginServiceChild::GetContentParent SendLaunchGMP "
"failed rv=0x%x",
static_cast<uint32_t>(rv)));
holder->Reject(rv, __func__);
MediaResult error(
rv,
nsPrintfCString("GeckoMediaPluginServiceChild::GetContentParent "
"SendLaunchGMPForNodeId failed with description (%s)",
errorDescription.get()));
LOGD(("%s", error.Description().get()));
holder->Reject(error, __func__);
return;
}
@ -120,9 +127,9 @@ GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
new GMPContentParent::CloseBlocker(parent));
holder->Resolve(blocker, __func__);
},
[rawHolder](nsresult rv) {
[rawHolder](MediaResult result) {
UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
holder->Reject(rv, __func__);
holder->Reject(result, __func__);
});
return promise;
@ -146,7 +153,9 @@ GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
nsTArray<nsCString> tags(aTags);
RefPtr<GMPCrashHelper> helper(aHelper);
RefPtr<GeckoMediaPluginServiceChild> self(this);
GetServiceChild()->Then(thread, __func__,
GetServiceChild()->Then(
thread,
__func__,
[self, nodeId, api, tags, helper, rawHolder](GMPServiceChild* child) {
UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
nsresult rv;
@ -158,6 +167,7 @@ GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
nsCString displayName;
uint32_t pluginId = 0;
ipc::Endpoint<PGMPContentParent> endpoint;
nsCString errorDescription = NS_LITERAL_CSTRING("");
bool ok = child->SendLaunchGMPForNodeId(nodeId,
api,
@ -167,7 +177,8 @@ GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
&otherProcess,
&displayName,
&endpoint,
&rv);
&rv,
&errorDescription);
if (helper && pluginId) {
// Note: Even if the launch failed, we need to connect the crash
@ -179,9 +190,14 @@ GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
}
if (!ok || NS_FAILED(rv)) {
LOGD(("GeckoMediaPluginServiceChild::GetContentParent SendLaunchGMP failed rv=%" PRIu32,
static_cast<uint32_t>(rv)));
holder->Reject(rv, __func__);
MediaResult error(
rv,
nsPrintfCString("GeckoMediaPluginServiceChild::GetContentParent "
"SendLaunchGMPForNodeId failed with description (%s)",
errorDescription.get()));
LOGD(("%s", error.Description().get()));
holder->Reject(error, __func__);
return;
}
@ -195,9 +211,9 @@ GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
RefPtr<GMPContentParent::CloseBlocker> blocker(new GMPContentParent::CloseBlocker(parent));
holder->Resolve(blocker, __func__);
},
[rawHolder](nsresult rv) {
[rawHolder](MediaResult result) {
UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
holder->Reject(rv, __func__);
holder->Reject(result, __func__);
});
return promise;

Просмотреть файл

@ -7,6 +7,7 @@
#define GMPServiceChild_h_
#include "GMPService.h"
#include "MediaResult.h"
#include "base/process.h"
#include "mozilla/ipc/Transport.h"
#include "mozilla/gmp/PGMPServiceChild.h"
@ -65,7 +66,8 @@ protected:
private:
friend class OpenPGMPServiceChild;
typedef MozPromise<GMPServiceChild*, nsresult, /* IsExclusive = */ true> GetServiceChildPromise;
typedef MozPromise<GMPServiceChild*, MediaResult, /* IsExclusive = */ true>
GetServiceChildPromise;
RefPtr<GetServiceChildPromise> GetServiceChild();
nsTArray<MozPromiseHolder<GetServiceChildPromise>> mGetServiceChildPromises;

Просмотреть файл

@ -1735,10 +1735,12 @@ GMPServiceParent::RecvLaunchGMP(const nsCString& aNodeId,
ProcessId* aOutProcessId,
nsCString* aOutDisplayName,
Endpoint<PGMPContentParent>* aOutEndpoint,
nsresult* aOutRv)
nsresult* aOutRv,
nsCString* aOutErrorDescription)
{
if (mService->IsShuttingDown()) {
*aOutRv = NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
*aOutErrorDescription = NS_LITERAL_CSTRING("Service is shutting down.");
return IPC_OK();
}
@ -1747,12 +1749,14 @@ GMPServiceParent::RecvLaunchGMP(const nsCString& aNodeId,
*aOutPluginId = gmp->GetPluginId();
} else {
*aOutRv = NS_ERROR_FAILURE;
*aOutErrorDescription = NS_LITERAL_CSTRING("SelectPluginForAPI returns nullptr.");
*aOutPluginId = 0;
return IPC_OK();
}
if (!gmp->EnsureProcessLoaded(aOutProcessId)) {
*aOutRv = NS_ERROR_FAILURE;
*aOutErrorDescription = NS_LITERAL_CSTRING("Process has not loaded.");
return IPC_OK();
}
@ -1765,9 +1769,12 @@ GMPServiceParent::RecvLaunchGMP(const nsCString& aNodeId,
Endpoint<PGMPContentParent> parent;
Endpoint<PGMPContentChild> child;
if (NS_WARN_IF(NS_FAILED(PGMPContent::CreateEndpoints(
OtherPid(), *aOutProcessId, &parent, &child)))) {
*aOutRv = NS_ERROR_FAILURE;
nsresult rv =
PGMPContent::CreateEndpoints(OtherPid(), *aOutProcessId, &parent, &child);
if (NS_WARN_IF(NS_FAILED(rv))) {
*aOutRv = rv;
*aOutErrorDescription =
NS_LITERAL_CSTRING("PGMPContent::CreateEndpoints failed.");
return IPC_OK();
}
@ -1775,6 +1782,8 @@ GMPServiceParent::RecvLaunchGMP(const nsCString& aNodeId,
if (!gmp->SendInitGMPContentChild(Move(child))) {
*aOutRv = NS_ERROR_FAILURE;
*aOutErrorDescription =
NS_LITERAL_CSTRING("SendInitGMPContentChild failed.");
return IPC_OK();
}
@ -1794,13 +1803,15 @@ GMPServiceParent::RecvLaunchGMPForNodeId(
ProcessId* aOutId,
nsCString* aOutDisplayName,
Endpoint<PGMPContentParent>* aOutEndpoint,
nsresult* aOutRv)
nsresult* aOutRv,
nsCString* aOutErrorDescription)
{
nsCString nodeId;
nsresult rv = mService->GetNodeId(
aNodeId.mOrigin(), aNodeId.mTopLevelOrigin(), aNodeId.mGMPName(), nodeId);
if (!NS_SUCCEEDED(rv)) {
*aOutRv = rv;
*aOutErrorDescription = NS_LITERAL_CSTRING("GetNodeId failed.");
return IPC_OK();
}
return RecvLaunchGMP(nodeId,
@ -1811,7 +1822,8 @@ GMPServiceParent::RecvLaunchGMPForNodeId(
aOutId,
aOutDisplayName,
aOutEndpoint,
aOutRv);
aOutRv,
aOutErrorDescription);
}
mozilla::ipc::IPCResult

Просмотреть файл

@ -246,7 +246,8 @@ public:
ProcessId* aOutID,
nsCString* aOutDisplayName,
Endpoint<PGMPContentParent>* aOutEndpoint,
nsresult* aOutRv) override;
nsresult* aOutRv,
nsCString* aOutErrorDescription) override;
ipc::IPCResult RecvLaunchGMPForNodeId(
const NodeIdData& nodeId,
@ -257,7 +258,8 @@ public:
ProcessId* aOutID,
nsCString* aOutDisplayName,
Endpoint<PGMPContentParent>* aOutEndpoint,
nsresult* aOutRv) override;
nsresult* aOutRv,
nsCString* aOutErrorDescription) override;
private:
void CloseTransport(Monitor* aSyncMonitor, bool* aCompleted);

Просмотреть файл

@ -22,7 +22,8 @@ parent:
ProcessId id,
nsCString displayName,
Endpoint<PGMPContentParent> endpoint,
nsresult aResult);
nsresult aResult,
nsCString aErrorDescription);
sync LaunchGMPForNodeId(NodeIdData nodeId,
nsCString api,
@ -32,7 +33,8 @@ parent:
ProcessId id,
nsCString displayName,
Endpoint<PGMPContentParent> endpoint,
nsresult aResult);
nsresult aResult,
nsCString aErrorDescription);
sync GetGMPNodeId(nsString origin, nsString topLevelOrigin, nsString gmpName)
returns (nsCString id);

Просмотреть файл

@ -305,8 +305,14 @@ const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
rt: "ruby"
};
const PARSE_CONTENT_MODE = {
NORMAL_CUE: "normal_cue",
PSUEDO_CUE: "pseudo_cue",
DOCUMENT_FRAGMENT: "document_fragment",
REGION_CUE: "region_cue",
}
// Parse content into a document fragment.
function parseContent(window, input, bReturnFrag) {
function parseContent(window, input, mode) {
function nextToken() {
// Check for end-of-string.
if (!input) {
@ -384,16 +390,20 @@ const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
return hours + ':' + minutes + ':' + seconds + '.' + f;
}
var isFirefoxSupportPseudo = (/firefox/i.test(window.navigator.userAgent))
&& Services.prefs.getBoolPref("media.webvtt.pseudo.enabled");
var root;
if (bReturnFrag) {
root = window.document.createDocumentFragment();
} else if (isFirefoxSupportPseudo) {
root = window.document.createElement("div", {pseudo: "::cue"});
} else {
root = window.document.createElement("div");
switch (mode) {
case PARSE_CONTENT_MODE.PSUEDO_CUE:
root = window.document.createElement("div", {pseudo: "::cue"});
break;
case PARSE_CONTENT_MODE.NORMAL_CUE:
case PARSE_CONTENT_MODE.REGION_CUE:
root = window.document.createElement("div");
break;
case PARSE_CONTENT_MODE.DOCUMENT_FRAGMENT:
root = window.document.createDocumentFragment();
break;
}
var current = root,
t,
tagStack = [];
@ -494,7 +504,11 @@ const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
// Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
// have inline positioning and will function as the cue background box.
this.cueDiv = parseContent(window, cue.text, false);
if (isFirefoxSupportPseudo) {
this.cueDiv = parseContent(window, cue.text, PARSE_CONTENT_MODE.PSUEDO_CUE);
} else {
this.cueDiv = parseContent(window, cue.text, PARSE_CONTENT_MODE.NORMAL_CUE);
}
var styles = {
color: color,
backgroundColor: backgroundColor,
@ -586,6 +600,79 @@ const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
CueStyleBox.prototype = _objCreate(StyleBox.prototype);
CueStyleBox.prototype.constructor = CueStyleBox;
function RegionNodeBox(window, region, container) {
StyleBox.call(this);
var boxLineHeight = container.height * 0.0533 // 0.0533vh ? 5.33vh
var boxHeight = boxLineHeight * region.lines;
var boxWidth = container.width * region.width / 100; // convert percentage to px
var regionNodeStyles = {
position: "absolute",
height: boxHeight + "px",
width: boxWidth + "px",
top: (region.viewportAnchorY * container.height / 100) - (region.regionAnchorY * boxHeight / 100) + "px",
left: (region.viewportAnchorX * container.width / 100) - (region.regionAnchorX * boxWidth / 100) + "px",
lineHeight: boxLineHeight + "px",
writingMode: "horizontal-tb",
backgroundColor: "rgba(0, 0, 0, 0.8)",
wordWrap: "break-word",
overflowWrap: "break-word",
font: (boxLineHeight/1.3) + "px sans-serif",
color: "rgba(255, 255, 255, 1)",
overflow: "hidden",
minHeight: "0px",
maxHeight: boxHeight + "px",
display: "inline-flex",
flexFlow: "column",
justifyContent: "flex-end",
};
this.div = window.document.createElement("div");
this.div.id = region.id; // useless?
this.applyStyles(regionNodeStyles);
}
RegionNodeBox.prototype = _objCreate(StyleBox.prototype);
RegionNodeBox.prototype.constructor = RegionNodeBox;
function RegionCueStyleBox(window, cue) {
StyleBox.call(this);
this.cueDiv = parseContent(window, cue.text, PARSE_CONTENT_MODE.REGION_CUE);
var regionCueStyles = {
position: "relative",
writingMode: "horizontal-tb",
unicodeBidi: "plaintext",
width: "auto",
height: "auto",
textAlign: cue.align,
};
// TODO: fix me, LTR and RTL ? using margin replace the "left/right"
// 6.1.14.3.3
var offset = cue.computedPosition * cue.region.width / 100;
// 6.1.14.3.4
switch (cue.align) {
case "start":
case "left":
regionCueStyles.left = offset + "%";
regionCueStyles.right = "auto";
break;
case "end":
case "right":
regionCueStyles.left = "auto";
regionCueStyles.right = offset + "%";
break;
case "middle":
break;
}
this.div = window.document.createElement("div");
this.applyStyles(regionCueStyles);
this.div.appendChild(this.cueDiv);
}
RegionCueStyleBox.prototype = _objCreate(StyleBox.prototype);
RegionCueStyleBox.prototype.constructor = RegionCueStyleBox;
// Represents the co-ordinates of an Element in a way that we can easily
// compute things with such as if it overlaps or intersects with another Element.
// Can initialize it with either a StyleBox or another BoxPosition.
@ -896,7 +983,7 @@ const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
if (!window) {
return null;
}
return parseContent(window, cuetext, true);
return parseContent(window, cuetext, PARSE_CONTENT_MODE.DOCUMENT_FRAGMENT);
};
var FONT_SIZE_PERCENT = 0.05;
@ -962,29 +1049,66 @@ const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
};
(function() {
var styleBox, cue;
var styleBox, cue, controlBarBox;
if (controlBarShown) {
controlBarBox = BoxPosition.getSimpleBoxPosition(controlBar);
// Add an empty output box that cover the same region as video control bar.
boxPositions.push(BoxPosition.getSimpleBoxPosition(controlBar));
boxPositions.push(controlBarBox);
}
// https://w3c.github.io/webvtt/#processing-model 6.1.12.1
// Create regionNode
var regionNodeBoxes = {};
var regionNodeBox;
for (var i = 0; i < cues.length; i++) {
cue = cues[i];
if (cue.region != null) {
// 6.1.14.1
styleBox = new RegionCueStyleBox(window, cue);
// Compute the intial position and styles of the cue div.
styleBox = new CueStyleBox(window, cue, styleOptions);
styleBox.cueDiv.style.setProperty("--cue-font-size", fontSize + "px");
rootOfCues.appendChild(styleBox.div);
if (!regionNodeBoxes[cue.region.id]) {
// create regionNode
// Adjust the container hieght to exclude the controlBar
var adjustContainerBox = BoxPosition.getSimpleBoxPosition(rootOfCues);
if (controlBarShown) {
adjustContainerBox.height -= controlBarBox.height;
adjustContainerBox.bottom += controlBarBox.height;
}
regionNodeBox = new RegionNodeBox(window, cue.region, adjustContainerBox);
regionNodeBoxes[cue.region.id] = regionNodeBox;
}
// 6.1.14.3
var currentRegionBox = regionNodeBoxes[cue.region.id];
var currentRegionNodeDiv = currentRegionBox.div;
// 6.1.14.3.2
// TODO: fix me, it looks like the we need to set/change "top" attribute at the styleBox.div
// to do the "scroll up", however, we do not implement it yet?
if (cue.region.scroll == "up" && currentRegionNodeDiv.childElementCount > 0) {
styleBox.div.style.transitionProperty = "top";
styleBox.div.style.transitionDuration = "0.433s";
}
// Move the cue div to it's correct line position.
moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
currentRegionNodeDiv.appendChild(styleBox.div);
rootOfCues.appendChild(currentRegionNodeDiv);
cue.displayState = styleBox.div;
boxPositions.push(BoxPosition.getSimpleBoxPosition(currentRegionBox));
} else {
// Compute the intial position and styles of the cue div.
styleBox = new CueStyleBox(window, cue, styleOptions);
styleBox.cueDiv.style.setProperty("--cue-font-size", fontSize + "px");
rootOfCues.appendChild(styleBox.div);
// Remember the computed div so that we don't have to recompute it later
// if we don't have too.
cue.displayState = styleBox.div;
// Move the cue div to it's correct line position.
moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
// Remember the computed div so that we don't have to recompute it later
// if we don't have too.
cue.displayState = styleBox.div;
boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
}
}
})();
};

Просмотреть файл

@ -4,9 +4,7 @@ default-preferences pref(dom.vr.puppet.enabled,true) pref(dom.vr.test.enabled,tr
# VR SubmitFrame is only implemented for D3D11.1 and MacOSX now.
# Our Windows 7 test machines don't support D3D11.1, so we run these tests on Windows 8+ only.
# We need to continue to investigate why these reftests can be run well in local,
# but will be suspended until terminating on reftest D3D11 debug build.
skip-if((!winWidget&&release_or_beta)||Android||gtkWidget||(winWidget&&isDebugBuild)||!layersGPUAccelerated||/^Windows\x20NT\x206\.1/.test(http.oscpu)) == draw_rect.html wrapper.html?draw_rect.png
skip-if((!winWidget&&release_or_beta)||Android||gtkWidget||!layersGPUAccelerated||/^Windows\x20NT\x206\.1/.test(http.oscpu)) == draw_rect.html wrapper.html?draw_rect.png
# On MacOSX platform, getting different color interpolation result.
# For lower resolution Mac hardware, we need to adjust it to fuzzy-if(cocoaWidget,1,1200).
fuzzy-if(cocoaWidget,1,600) skip-if((!winWidget&&release_or_beta)||Android||gtkWidget||(winWidget&&isDebugBuild)||!layersGPUAccelerated||/^Windows\x20NT\x206\.1/.test(http.oscpu)) == change_size.html wrapper.html?change_size.png
fuzzy-if(cocoaWidget,1,600) skip-if((!winWidget&&release_or_beta)||Android||gtkWidget||!layersGPUAccelerated||/^Windows\x20NT\x206\.1/.test(http.oscpu)) == change_size.html wrapper.html?change_size.png

Просмотреть файл

@ -2384,23 +2384,18 @@ EditorBase::ScrollSelectionIntoView(bool aScrollToAnchor)
return NS_OK;
}
void
EditorBase::FindBetterInsertionPoint(nsCOMPtr<nsIDOMNode>& aNode,
int32_t& aOffset)
EditorRawDOMPoint
EditorBase::FindBetterInsertionPoint(const EditorRawDOMPoint& aPoint)
{
nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
FindBetterInsertionPoint(node, aOffset, nullptr);
aNode = do_QueryInterface(node);
}
if (NS_WARN_IF(!aPoint.IsSet())) {
return aPoint;
}
void
EditorBase::FindBetterInsertionPoint(nsCOMPtr<nsINode>& aNode,
int32_t& aOffset,
nsCOMPtr<nsIContent>* aSelChild)
{
if (aNode->IsNodeOfType(nsINode::eTEXT)) {
MOZ_ASSERT(aPoint.IsSetAndValid());
if (aPoint.Container()->IsNodeOfType(nsINode::eTEXT)) {
// There is no "better" insertion point.
return;
return aPoint;
}
if (!IsPlaintextEditor()) {
@ -2408,57 +2403,44 @@ EditorBase::FindBetterInsertionPoint(nsCOMPtr<nsINode>& aNode,
// WARNING: When you add some code to find better node in HTML editor,
// you need to call this before calling InsertTextImpl() in
// HTMLEditRules.
return;
return aPoint;
}
nsCOMPtr<nsINode> node = aNode;
int32_t offset = aOffset;
nsCOMPtr<nsINode> root = GetRoot();
if (aNode == root) {
if (aPoint.Container() == root) {
// In some cases, aNode is the anonymous DIV, and offset is 0. To avoid
// injecting unneeded text nodes, we first look to see if we have one
// available. In that case, we'll just adjust node and offset accordingly.
if (!offset && node->HasChildren() &&
node->GetFirstChild()->IsNodeOfType(nsINode::eTEXT)) {
aNode = node->GetFirstChild();
aOffset = 0;
if (aSelChild) {
*aSelChild = nullptr;
}
return;
if (aPoint.IsStartOfContainer() &&
aPoint.Container()->HasChildren() &&
aPoint.Container()->GetFirstChild()->IsNodeOfType(nsINode::eTEXT)) {
return EditorRawDOMPoint(aPoint.Container()->GetFirstChild(), 0);
}
// In some other cases, aNode is the anonymous DIV, and offset points to the
// terminating mozBR. In that case, we'll adjust aInOutNode and
// aInOutOffset to the preceding text node, if any.
if (offset) {
if (!aPoint.IsStartOfContainer()) {
if (AsHTMLEditor()) {
// Fall back to a slow path that uses GetChildAt() for Thunderbird's
// plaintext editor.
nsIContent* child = node->GetChildAt(offset - 1);
nsIContent* child = aPoint.GetPreviousSiblingOfChildAtOffset();
if (child && child->IsNodeOfType(nsINode::eTEXT)) {
NS_ENSURE_TRUE_VOID(node->Length() <= INT32_MAX);
aNode = child;
aOffset = static_cast<int32_t>(aNode->Length());
if (aSelChild) {
*aSelChild = nullptr;
if (NS_WARN_IF(child->Length() > INT32_MAX)) {
return aPoint;
}
return;
return EditorRawDOMPoint(child, child->Length());
}
} else {
// If we're in a real plaintext editor, use a fast path that avoids
// calling GetChildAt() which may perform a linear search.
nsIContent* child = node->GetLastChild();
nsIContent* child = aPoint.Container()->GetLastChild();
while (child) {
if (child->IsNodeOfType(nsINode::eTEXT)) {
NS_ENSURE_TRUE_VOID(node->Length() <= INT32_MAX);
aNode = child;
aOffset = static_cast<int32_t>(aNode->Length());
if (aSelChild) {
*aSelChild = nullptr;
if (NS_WARN_IF(child->Length() > INT32_MAX)) {
return aPoint;
}
return;
return EditorRawDOMPoint(child, child->Length());
}
child = child->GetPreviousSibling();
}
@ -2469,125 +2451,130 @@ EditorBase::FindBetterInsertionPoint(nsCOMPtr<nsINode>& aNode,
// Sometimes, aNode is the mozBR element itself. In that case, we'll adjust
// the insertion point to the previous text node, if one exists, or to the
// parent anonymous DIV.
if (TextEditUtils::IsMozBR(node) && !offset) {
if (node->GetPreviousSibling() &&
node->GetPreviousSibling()->IsNodeOfType(nsINode::eTEXT)) {
NS_ENSURE_TRUE_VOID(node->Length() <= INT32_MAX);
aNode = node->GetPreviousSibling();
aOffset = static_cast<int32_t>(aNode->Length());
if (aSelChild) {
*aSelChild = nullptr;
if (TextEditUtils::IsMozBR(aPoint.Container()) &&
aPoint.IsStartOfContainer()) {
nsIContent* previousSibling = aPoint.Container()->GetPreviousSibling();
if (previousSibling && previousSibling->IsNodeOfType(nsINode::eTEXT)) {
if (NS_WARN_IF(previousSibling->Length() > INT32_MAX)) {
return aPoint;
}
return;
return EditorRawDOMPoint(previousSibling, previousSibling->Length());
}
if (node->GetParentNode() && node->GetParentNode() == root) {
if (aSelChild) {
*aSelChild = node->AsContent();
}
aNode = node->GetParentNode();
aOffset = 0;
return;
nsINode* parentOfContainer = aPoint.Container()->GetParentNode();
if (parentOfContainer && parentOfContainer == root) {
return EditorRawDOMPoint(parentOfContainer,
aPoint.Container()->AsContent(), 0);
}
}
return aPoint;
}
nsresult
EditorBase::InsertTextImpl(const nsAString& aStringToInsert,
nsCOMPtr<nsINode>* aInOutNode,
nsCOMPtr<nsIContent>* aInOutChildAtOffset,
int32_t* aInOutOffset,
nsIDocument* aDoc)
EditorBase::InsertTextImpl(nsIDocument& aDocument,
const nsAString& aStringToInsert,
const EditorRawDOMPoint& aPointToInsert,
EditorRawDOMPoint* aPointAfterInsertedString)
{
// NOTE: caller *must* have already used AutoTransactionsConserveSelection
// stack-based class to turn off txn selection updating. Caller also turned
// on rules sniffing if desired.
NS_ENSURE_TRUE(aInOutNode && *aInOutNode && aInOutOffset && aDoc,
NS_ERROR_NULL_POINTER);
if (NS_WARN_IF(!aPointToInsert.IsSet())) {
return NS_ERROR_INVALID_ARG;
}
MOZ_ASSERT(aPointToInsert.IsSetAndValid());
if (!ShouldHandleIMEComposition() && aStringToInsert.IsEmpty()) {
if (aPointAfterInsertedString) {
*aPointAfterInsertedString = aPointToInsert;
}
return NS_OK;
}
// This method doesn't support over INT32_MAX length text since aInOutOffset
// is int32_t*.
CheckedInt<int32_t> lengthToInsert(aStringToInsert.Length());
NS_ENSURE_TRUE(lengthToInsert.isValid(), NS_ERROR_INVALID_ARG);
nsCOMPtr<nsINode> node = *aInOutNode;
int32_t offset = *aInOutOffset;
nsCOMPtr<nsIContent> child = *aInOutChildAtOffset;
MOZ_ASSERT(!node->IsContainerNode() ||
node->Length() == static_cast<uint32_t>(offset) ||
node->GetChildAt(offset) == *aInOutChildAtOffset,
"|child| must be a child node at |offset| in |node| unless it's a text "
"or some other data node, or after the last child");
if (NS_WARN_IF(!lengthToInsert.isValid())) {
return NS_ERROR_INVALID_ARG;
}
// In some cases, the node may be the anonymous div elemnt or a mozBR
// element. Let's try to look for better insertion point in the nearest
// text node if there is.
FindBetterInsertionPoint(node, offset, address_of(child));
EditorRawDOMPoint pointToInsert = FindBetterInsertionPoint(aPointToInsert);
// If a neighboring text node already exists, use that
if (!node->IsNodeOfType(nsINode::eTEXT)) {
if (offset && child && child->GetPreviousSibling() &&
child->GetPreviousSibling()->IsNodeOfType(nsINode::eTEXT)) {
node = child->GetPreviousSibling();
offset = node->Length();
} else if (offset < static_cast<int32_t>(node->Length()) &&
child && child->IsNodeOfType(nsINode::eTEXT)) {
node = child;
offset = 0;
if (!pointToInsert.Container()->IsNodeOfType(nsINode::eTEXT)) {
nsIContent* child = nullptr;
if (!pointToInsert.IsStartOfContainer() &&
(child = pointToInsert.GetPreviousSiblingOfChildAtOffset()) &&
child->IsNodeOfType(nsINode::eTEXT)) {
pointToInsert.Set(child, child->Length());
} else if (!pointToInsert.IsEndOfContainer() &&
(child = pointToInsert.GetChildAtOffset()) &&
child->IsNodeOfType(nsINode::eTEXT)) {
pointToInsert.Set(child, 0);
}
}
if (ShouldHandleIMEComposition()) {
CheckedInt<int32_t> newOffset;
if (!node->IsNodeOfType(nsINode::eTEXT)) {
if (!pointToInsert.Container()->IsNodeOfType(nsINode::eTEXT)) {
// create a text node
RefPtr<nsTextNode> newNode =
EditorBase::CreateTextNode(*aDoc, EmptyString());
EditorBase::CreateTextNode(aDocument, EmptyString());
// then we insert it into the dom tree
nsresult rv = InsertNode(*newNode, *node, offset);
nsresult rv = InsertNode(*newNode, *pointToInsert.Container(),
pointToInsert.Offset());
NS_ENSURE_SUCCESS(rv, rv);
node = newNode;
offset = 0;
pointToInsert.Set(newNode, 0);
newOffset = lengthToInsert;
} else {
newOffset = lengthToInsert + offset;
newOffset = lengthToInsert + pointToInsert.Offset();
NS_ENSURE_TRUE(newOffset.isValid(), NS_ERROR_FAILURE);
}
nsresult rv =
InsertTextIntoTextNodeImpl(aStringToInsert, *node->GetAsText(), offset);
InsertTextIntoTextNodeImpl(aStringToInsert,
*pointToInsert.Container()->GetAsText(),
pointToInsert.Offset());
NS_ENSURE_SUCCESS(rv, rv);
offset = newOffset.value();
} else {
if (node->IsNodeOfType(nsINode::eTEXT)) {
CheckedInt<int32_t> newOffset = lengthToInsert + offset;
NS_ENSURE_TRUE(newOffset.isValid(), NS_ERROR_FAILURE);
// we are inserting text into an existing text node.
nsresult rv =
InsertTextIntoTextNodeImpl(aStringToInsert, *node->GetAsText(), offset);
NS_ENSURE_SUCCESS(rv, rv);
offset = newOffset.value();
} else {
// we are inserting text into a non-text node. first we have to create a
// textnode (this also populates it with the text)
RefPtr<nsTextNode> newNode =
EditorBase::CreateTextNode(*aDoc, aStringToInsert);
// then we insert it into the dom tree
nsresult rv = InsertNode(*newNode, *node, offset);
NS_ENSURE_SUCCESS(rv, rv);
node = newNode;
offset = lengthToInsert.value();
if (aPointAfterInsertedString) {
aPointAfterInsertedString->Set(pointToInsert.Container(),
newOffset.value());
}
return NS_OK;
}
*aInOutNode = node;
*aInOutOffset = offset;
*aInOutChildAtOffset = nullptr;
if (pointToInsert.Container()->IsNodeOfType(nsINode::eTEXT)) {
CheckedInt<int32_t> newOffset = lengthToInsert + pointToInsert.Offset();
NS_ENSURE_TRUE(newOffset.isValid(), NS_ERROR_FAILURE);
// we are inserting text into an existing text node.
nsresult rv =
InsertTextIntoTextNodeImpl(aStringToInsert,
*pointToInsert.Container()->GetAsText(),
pointToInsert.Offset());
NS_ENSURE_SUCCESS(rv, rv);
if (aPointAfterInsertedString) {
aPointAfterInsertedString->Set(pointToInsert.Container(),
newOffset.value());
}
return NS_OK;
}
// we are inserting text into a non-text node. first we have to create a
// textnode (this also populates it with the text)
RefPtr<nsTextNode> newNode =
EditorBase::CreateTextNode(aDocument, aStringToInsert);
// then we insert it into the dom tree
nsresult rv = InsertNode(*newNode, *pointToInsert.Container(),
pointToInsert.Offset());
NS_ENSURE_SUCCESS(rv, rv);
if (aPointAfterInsertedString) {
aPointAfterInsertedString->Set(newNode, lengthToInsert.value());
}
return NS_OK;
}
@ -2693,7 +2680,7 @@ EditorBase::GetFirstEditableNode(nsINode* aRoot)
nsIContent* node = GetLeftmostChild(aRoot);
if (node && !IsEditable(node)) {
node = GetNextNode(node, /* aEditableNode = */ true);
node = GetNextEditableNode(*node);
}
return (node != aRoot) ? node : nullptr;
@ -3298,104 +3285,129 @@ EditorBase::GetLengthOfDOMNode(nsIDOMNode* aNode,
}
nsIContent*
EditorBase::GetPriorNode(nsINode* aParentNode,
int32_t aOffset,
nsINode* aChildAtOffset,
bool aEditableNode,
bool aNoBlockCrossing)
EditorBase::GetPreviousNodeInternal(nsINode& aNode,
bool aFindEditableNode,
bool aNoBlockCrossing)
{
MOZ_ASSERT(aParentNode);
if (!IsDescendantOfEditorRoot(&aNode)) {
return nullptr;
}
return FindNode(&aNode, false, aFindEditableNode, aNoBlockCrossing);
}
nsIContent*
EditorBase::GetPreviousNodeInternal(const EditorRawDOMPoint& aPoint,
bool aFindEditableNode,
bool aNoBlockCrossing)
{
MOZ_ASSERT(aPoint.IsSetAndValid());
NS_WARNING_ASSERTION(!aPoint.Container()->IsNodeOfType(nsINode::eDATA_NODE) ||
aPoint.Container()->IsNodeOfType(nsINode::eTEXT),
"GetPreviousNodeInternal() doesn't assume that the start point is a "
"data node except text node");
// If we are at the beginning of the node, or it is a text node, then just
// look before it.
if (!aOffset || aParentNode->NodeType() == nsIDOMNode::TEXT_NODE) {
if (aNoBlockCrossing && IsBlockNode(aParentNode)) {
if (aPoint.IsStartOfContainer() ||
aPoint.Container()->IsNodeOfType(nsINode::eTEXT)) {
if (aNoBlockCrossing && IsBlockNode(aPoint.Container())) {
// If we aren't allowed to cross blocks, don't look before this block.
return nullptr;
}
return GetPriorNode(aParentNode, aEditableNode, aNoBlockCrossing);
return GetPreviousNodeInternal(*aPoint.Container(),
aFindEditableNode, aNoBlockCrossing);
}
// else look before the child at 'aOffset'
if (aChildAtOffset) {
return GetPriorNode(aChildAtOffset, aEditableNode, aNoBlockCrossing);
if (aPoint.GetChildAtOffset()) {
return GetPreviousNodeInternal(*aPoint.GetChildAtOffset(),
aFindEditableNode, aNoBlockCrossing);
}
// unless there isn't one, in which case we are at the end of the node
// and want the deep-right child.
nsIContent* resultNode = GetRightmostChild(aParentNode, aNoBlockCrossing);
if (!resultNode || !aEditableNode || IsEditable(resultNode)) {
return resultNode;
nsIContent* rightMostNode =
GetRightmostChild(aPoint.Container(), aNoBlockCrossing);
if (!rightMostNode) {
return nullptr;
}
if (!aFindEditableNode || IsEditable(rightMostNode)) {
return rightMostNode;
}
// restart the search from the non-editable node we just found
return GetPriorNode(resultNode, aEditableNode, aNoBlockCrossing);
return GetPreviousNodeInternal(*rightMostNode,
aFindEditableNode, aNoBlockCrossing);
}
nsIContent*
EditorBase::GetNextNode(nsINode* aParentNode,
int32_t aOffset,
nsINode* aChildAtOffset,
bool aEditableNode,
bool aNoBlockCrossing)
EditorBase::GetNextNodeInternal(nsINode& aNode,
bool aFindEditableNode,
bool aNoBlockCrossing)
{
MOZ_ASSERT(aParentNode);
if (!IsDescendantOfEditorRoot(&aNode)) {
return nullptr;
}
return FindNode(&aNode, true, aFindEditableNode, aNoBlockCrossing);
}
// if aParentNode is a text node, use its location instead
if (aParentNode->NodeType() == nsIDOMNode::TEXT_NODE) {
nsINode* parent = aParentNode->GetParentNode();
NS_ENSURE_TRUE(parent, nullptr);
aOffset = parent->IndexOf(aParentNode) + 1; // _after_ the text node
aParentNode = parent;
nsIContent*
EditorBase::GetNextNodeInternal(const EditorRawDOMPoint& aPoint,
bool aFindEditableNode,
bool aNoBlockCrossing)
{
MOZ_ASSERT(aPoint.IsSetAndValid());
NS_WARNING_ASSERTION(!aPoint.Container()->IsNodeOfType(nsINode::eDATA_NODE) ||
aPoint.Container()->IsNodeOfType(nsINode::eTEXT),
"GetNextNodeInternal() doesn't assume that the start point is a "
"data node except text node");
EditorRawDOMPoint point(aPoint);
// if the container is a text node, use its location instead
if (point.Container()->IsNodeOfType(nsINode::eTEXT)) {
point.Set(point.Container());
bool advanced = point.AdvanceOffset();
if (NS_WARN_IF(!advanced)) {
return nullptr;
}
}
// look at the child at 'aOffset'
if (aChildAtOffset) {
if (aNoBlockCrossing && IsBlockNode(aChildAtOffset)) {
MOZ_ASSERT(aChildAtOffset->IsContent());
return aChildAtOffset->AsContent();
if (point.GetChildAtOffset()) {
if (aNoBlockCrossing && IsBlockNode(point.GetChildAtOffset())) {
return point.GetChildAtOffset();
}
nsIContent* resultNode = GetLeftmostChild(aChildAtOffset, aNoBlockCrossing);
if (!resultNode) {
MOZ_ASSERT(aChildAtOffset->IsContent());
return aChildAtOffset->AsContent();
nsIContent* leftMostNode =
GetLeftmostChild(point.GetChildAtOffset(), aNoBlockCrossing);
if (!leftMostNode) {
return point.GetChildAtOffset();
}
if (!IsDescendantOfEditorRoot(resultNode)) {
if (!IsDescendantOfEditorRoot(leftMostNode)) {
return nullptr;
}
if (!aEditableNode || IsEditable(resultNode)) {
return resultNode;
if (!aFindEditableNode || IsEditable(leftMostNode)) {
return leftMostNode;
}
// restart the search from the non-editable node we just found
return GetNextNode(resultNode, aEditableNode, aNoBlockCrossing);
return GetNextNodeInternal(*leftMostNode,
aFindEditableNode, aNoBlockCrossing);
}
// unless there isn't one, in which case we are at the end of the node
// and want the next one.
if (aNoBlockCrossing && IsBlockNode(aParentNode)) {
if (aNoBlockCrossing && IsBlockNode(point.Container())) {
// don't cross out of parent block
return nullptr;
}
return GetNextNode(aParentNode, aEditableNode, aNoBlockCrossing);
}
nsIContent*
EditorBase::GetPriorNode(nsINode* aCurrentNode,
bool aEditableNode,
bool aNoBlockCrossing /* = false */)
{
MOZ_ASSERT(aCurrentNode);
if (!IsDescendantOfEditorRoot(aCurrentNode)) {
return nullptr;
}
return FindNode(aCurrentNode, false, aEditableNode, aNoBlockCrossing);
return GetNextNodeInternal(*point.Container(),
aFindEditableNode, aNoBlockCrossing);
}
nsIContent*
@ -3450,20 +3462,6 @@ EditorBase::FindNextLeafNode(nsINode* aCurrentNode,
return nullptr;
}
nsIContent*
EditorBase::GetNextNode(nsINode* aCurrentNode,
bool aEditableNode,
bool bNoBlockCrossing)
{
MOZ_ASSERT(aCurrentNode);
if (!IsDescendantOfEditorRoot(aCurrentNode)) {
return nullptr;
}
return FindNode(aCurrentNode, true, aEditableNode, bNoBlockCrossing);
}
nsIContent*
EditorBase::FindNode(nsINode* aCurrentNode,
bool aGoForward,
@ -3824,20 +3822,35 @@ EditorBase::GetStartNodeAndOffset(Selection* aSelection,
*aStartContainer = nullptr;
*aStartOffset = 0;
if (!aSelection->RangeCount()) {
EditorRawDOMPoint point = EditorBase::GetStartPoint(aSelection);
if (!point.IsSet()) {
return NS_ERROR_FAILURE;
}
const nsRange* range = aSelection->GetRangeAt(0);
NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
NS_ENSURE_TRUE(range->IsPositioned(), NS_ERROR_FAILURE);
NS_IF_ADDREF(*aStartContainer = range->GetStartContainer());
*aStartOffset = range->StartOffset();
NS_ADDREF(*aStartContainer = point.Container());
*aStartOffset = point.Offset();
return NS_OK;
}
// static
EditorRawDOMPoint
EditorBase::GetStartPoint(Selection* aSelection)
{
MOZ_ASSERT(aSelection);
if (NS_WARN_IF(!aSelection->RangeCount())) {
return EditorRawDOMPoint();
}
const nsRange* range = aSelection->GetRangeAt(0);
if (NS_WARN_IF(!range) ||
NS_WARN_IF(!range->IsPositioned())) {
return EditorRawDOMPoint();
}
return EditorRawDOMPoint(range->StartRef());
}
/**
* GetEndNodeAndOffset() returns whatever the end parent & offset is of
* the first range in the selection.
@ -3874,16 +3887,33 @@ EditorBase::GetEndNodeAndOffset(Selection* aSelection,
*aEndContainer = nullptr;
*aEndOffset = 0;
NS_ENSURE_TRUE(aSelection->RangeCount(), NS_ERROR_FAILURE);
EditorRawDOMPoint point = EditorBase::GetEndPoint(aSelection);
if (!point.IsSet()) {
return NS_ERROR_FAILURE;
}
NS_ADDREF(*aEndContainer = point.Container());
*aEndOffset = point.Offset();
return NS_OK;
}
// static
EditorRawDOMPoint
EditorBase::GetEndPoint(Selection* aSelection)
{
MOZ_ASSERT(aSelection);
if (NS_WARN_IF(!aSelection->RangeCount())) {
return EditorRawDOMPoint();
}
const nsRange* range = aSelection->GetRangeAt(0);
NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
if (NS_WARN_IF(!range) ||
NS_WARN_IF(!range->IsPositioned())) {
return EditorRawDOMPoint();
}
NS_ENSURE_TRUE(range->IsPositioned(), NS_ERROR_FAILURE);
NS_IF_ADDREF(*aEndContainer = range->GetEndContainer());
*aEndOffset = range->EndOffset();
return NS_OK;
return EditorRawDOMPoint(range->EndRef());
}
nsresult
@ -4580,7 +4610,7 @@ EditorBase::CreateTxnForDeleteRange(nsRange* aRangeToDelete,
if (aAction == ePrevious && isFirst) {
// we're backspacing from the beginning of the node. Delete the first
// thing to our left
nsCOMPtr<nsIContent> priorNode = GetPriorNode(node, true);
nsCOMPtr<nsIContent> priorNode = GetPreviousEditableNode(*node);
if (NS_WARN_IF(!priorNode)) {
return nullptr;
}
@ -4619,7 +4649,7 @@ EditorBase::CreateTxnForDeleteRange(nsRange* aRangeToDelete,
if (aAction == eNext && isLast) {
// we're deleting from the end of the node. Delete the first thing to our
// right
nsCOMPtr<nsIContent> nextNode = GetNextNode(node, true);
nsCOMPtr<nsIContent> nextNode = GetNextEditableNode(*node);
if (NS_WARN_IF(!nextNode)) {
return nullptr;
}
@ -4674,9 +4704,10 @@ EditorBase::CreateTxnForDeleteRange(nsRange* aRangeToDelete,
// node to find out
nsCOMPtr<nsINode> selectedNode;
if (aAction == ePrevious) {
selectedNode = GetPriorNode(node, offset, child, true);
selectedNode =
GetPreviousEditableNode(EditorRawDOMPoint(node, child, offset));
} else if (aAction == eNext) {
selectedNode = GetNextNode(node, offset, child, true);
selectedNode = GetNextEditableNode(EditorRawDOMPoint(node, child, offset));
}
while (selectedNode &&
@ -4684,9 +4715,9 @@ EditorBase::CreateTxnForDeleteRange(nsRange* aRangeToDelete,
!selectedNode->Length()) {
// Can't delete an empty chardata node (bug 762183)
if (aAction == ePrevious) {
selectedNode = GetPriorNode(selectedNode, true);
selectedNode = GetPreviousEditableNode(*selectedNode);
} else if (aAction == eNext) {
selectedNode = GetNextNode(selectedNode, true);
selectedNode = GetNextEditableNode(*selectedNode);
}
}
@ -4978,10 +5009,10 @@ EditorBase::InitializeSelection(nsIDOMEventTarget* aFocusEventTarget)
// XXX If selection is changed during reframe, this doesn't work well!
nsRange* firstRange = selection->GetRangeAt(0);
NS_ENSURE_TRUE(firstRange, NS_ERROR_FAILURE);
nsCOMPtr<nsINode> startNode = firstRange->GetStartContainer();
int32_t startOffset = firstRange->StartOffset();
FindBetterInsertionPoint(startNode, startOffset, nullptr);
Text* textNode = startNode->GetAsText();
EditorRawDOMPoint atStartOfFirstRange(firstRange->StartRef());
EditorRawDOMPoint betterInsertionPoint =
FindBetterInsertionPoint(atStartOfFirstRange);
Text* textNode = betterInsertionPoint.Container()->GetAsText();
MOZ_ASSERT(textNode,
"There must be text node if mIMETextLength is larger than 0");
if (textNode) {

Просмотреть файл

@ -286,11 +286,32 @@ public:
public:
virtual bool IsModifiableNode(nsINode* aNode);
virtual nsresult InsertTextImpl(const nsAString& aStringToInsert,
nsCOMPtr<nsINode>* aInOutNode,
nsCOMPtr<nsIContent>* aInOutChildAtOffset,
int32_t* aInOutOffset,
nsIDocument* aDoc);
/**
* InsertTextImpl() inserts aStringToInsert to aPointToInsert or better
* insertion point around it. If aPointToInsert isn't in a text node,
* this method looks for the nearest point in a text node with
* FindBetterInsertionPoint(). If there is no text node, this creates
* new text node and put aStringToInsert to it.
*
* @param aDocument The document of this editor.
* @param aStringToInsert The string to insert.
* @param aPointToInser The point to insert aStringToInsert.
* Must be valid DOM point.
* @param aPointAfterInsertedString
* The point after inserted aStringToInsert.
* So, when this method actually inserts string,
* this is set to a point in the text node.
* Otherwise, this may be set to aPointToInsert.
* @return When this succeeds to insert the string or
* does nothing during composition, returns NS_OK.
* Otherwise, an error code.
*/
virtual nsresult
InsertTextImpl(nsIDocument& aDocument,
const nsAString& aStringToInsert,
const EditorRawDOMPoint& aPointToInsert,
EditorRawDOMPoint* aPointAfterInsertedString = nullptr);
nsresult InsertTextIntoTextNodeImpl(const nsAString& aStringToInsert,
Text& aTextNode, int32_t aOffset,
bool aSuppressIME = false);
@ -586,11 +607,60 @@ protected:
virtual bool IsBlockNode(nsINode* aNode);
/**
* Helper for GetPriorNode() and GetNextNode().
* Helper for GetPreviousNodeInternal() and GetNextNodeInternal().
*/
nsIContent* FindNextLeafNode(nsINode* aCurrentNode,
bool aGoForward,
bool bNoBlockCrossing);
nsIContent* FindNode(nsINode* aCurrentNode,
bool aGoForward,
bool aEditableNode,
bool bNoBlockCrossing);
/**
* Get the node immediately previous node of aNode.
* @param atNode The node from which we start the search.
* @param aFindEditableNode If true, only return an editable node.
* @param aNoBlockCrossing If true, don't move across "block" nodes,
* whatever that means.
* @return The node that occurs before aNode in
* the tree, skipping non-editable nodes if
* aFindEditableNode is true. If there is no
* previous node, returns nullptr.
*/
nsIContent* GetPreviousNodeInternal(nsINode& aNode,
bool aFindEditableNode,
bool aNoBlockCrossing);
/**
* And another version that takes a point in DOM tree rather than a node.
*/
nsIContent* GetPreviousNodeInternal(const EditorRawDOMPoint& aPoint,
bool aFindEditableNode,
bool aNoBlockCrossing);
/**
* Get the node immediately next node of aNode.
* @param aNode The node from which we start the search.
* @param aFindEditableNode If true, only return an editable node.
* @param aNoBlockCrossing If true, don't move across "block" nodes,
* whatever that means.
* @return The node that occurs after aNode in the
* tree, skipping non-editable nodes if
* aFindEditableNode is true. If there is no
* next node, returns nullptr.
*/
nsIContent* GetNextNodeInternal(nsINode& aNode,
bool aFindEditableNode,
bool bNoBlockCrossing);
/**
* And another version that takes a point in DOM tree rather than a node.
*/
nsIContent* GetNextNodeInternal(const EditorRawDOMPoint& aPoint,
bool aFindEditableNode,
bool aNoBlockCrossing);
virtual nsresult InstallEventListeners();
virtual void CreateEventListeners();
@ -716,58 +786,102 @@ public:
static nsresult GetLengthOfDOMNode(nsIDOMNode *aNode, uint32_t &aCount);
/**
* Get the node immediately prior to aCurrentNode.
* @param aCurrentNode the node from which we start the search
* @param aEditableNode if true, only return an editable node
* @param aResultNode [OUT] the node that occurs before aCurrentNode in
* the tree, skipping non-editable nodes if
* aEditableNode is true. If there is no prior
* node, aResultNode will be nullptr.
* @param bNoBlockCrossing If true, don't move across "block" nodes,
* whatever that means.
* Get the previous node.
*/
nsIContent* GetPriorNode(nsINode* aCurrentNode, bool aEditableNode,
bool aNoBlockCrossing = false);
nsIContent* GetPreviousNode(const EditorRawDOMPoint& aPoint)
{
return GetPreviousNodeInternal(aPoint, false, false);
}
nsIContent* GetPreviousEditableNode(const EditorRawDOMPoint& aPoint)
{
return GetPreviousNodeInternal(aPoint, true, false);
}
nsIContent* GetPreviousNodeInBlock(const EditorRawDOMPoint& aPoint)
{
return GetPreviousNodeInternal(aPoint, false, true);
}
nsIContent* GetPreviousEditableNodeInBlock(
const EditorRawDOMPoint& aPoint)
{
return GetPreviousNodeInternal(aPoint, true, true);
}
nsIContent* GetPreviousNode(nsINode& aNode)
{
return GetPreviousNodeInternal(aNode, false, false);
}
nsIContent* GetPreviousEditableNode(nsINode& aNode)
{
return GetPreviousNodeInternal(aNode, true, false);
}
nsIContent* GetPreviousNodeInBlock(nsINode& aNode)
{
return GetPreviousNodeInternal(aNode, false, true);
}
nsIContent* GetPreviousEditableNodeInBlock(nsINode& aNode)
{
return GetPreviousNodeInternal(aNode, true, true);
}
/**
* And another version that takes a {parent,offset} pair rather than a node.
* Get the next node.
*
* Note that methods taking EditorRawDOMPoint behavior includes the
* child at offset as search target. E.g., following code causes infinite
* loop.
*
* EditorRawDOMPoint point(aEditableNode);
* while (nsIContent* content = GetNextEditableNode(point)) {
* // Do something...
* point.Set(content);
* }
*
* Following code must be you expected:
*
* while (nsIContent* content = GetNextEditableNode(point)) {
* // Do something...
* DebugOnly<bool> advanced = point.Advanced();
* MOZ_ASSERT(advanced);
* point.Set(point.GetChildAtOffset());
* }
*
* On the other hand, the methods taking nsINode behavior must be what
* you want. They start to search the result from next node of the given
* node.
*/
nsIContent* GetPriorNode(nsINode* aParentNode,
int32_t aOffset,
nsINode* aChildAtOffset,
bool aEditableNode,
bool aNoBlockCrossing = false);
nsIContent* GetNextNode(const EditorRawDOMPoint& aPoint)
{
return GetNextNodeInternal(aPoint, false, false);
}
nsIContent* GetNextEditableNode(const EditorRawDOMPoint& aPoint)
{
return GetNextNodeInternal(aPoint, true, false);
}
nsIContent* GetNextNodeInBlock(const EditorRawDOMPoint& aPoint)
{
return GetNextNodeInternal(aPoint, false, true);
}
nsIContent* GetNextEditableNodeInBlock(
const EditorRawDOMPoint& aPoint)
{
return GetNextNodeInternal(aPoint, true, true);
}
nsIContent* GetNextNode(nsINode& aNode)
{
return GetNextNodeInternal(aNode, false, false);
}
nsIContent* GetNextEditableNode(nsINode& aNode)
{
return GetNextNodeInternal(aNode, true, false);
}
nsIContent* GetNextNodeInBlock(nsINode& aNode)
{
return GetNextNodeInternal(aNode, false, true);
}
nsIContent* GetNextEditableNodeInBlock(nsINode& aNode)
{
return GetNextNodeInternal(aNode, true, true);
}
/**
* Get the node immediately after to aCurrentNode.
* @param aCurrentNode the node from which we start the search
* @param aEditableNode if true, only return an editable node
* @param aResultNode [OUT] the node that occurs after aCurrentNode in the
* tree, skipping non-editable nodes if
* aEditableNode is true. If there is no prior
* node, aResultNode will be nullptr.
*/
nsIContent* GetNextNode(nsINode* aCurrentNode,
bool aEditableNode,
bool bNoBlockCrossing = false);
/**
* And another version that takes a {parent,offset} pair rather than a node.
*/
nsIContent* GetNextNode(nsINode* aParentNode,
int32_t aOffset,
nsINode* aChildAtOffset,
bool aEditableNode,
bool aNoBlockCrossing = false);
/**
* Helper for GetNextNode() and GetPriorNode().
*/
nsIContent* FindNode(nsINode* aCurrentNode,
bool aGoForward,
bool aEditableNode,
bool bNoBlockCrossing);
/**
* Get the rightmost child of aCurrentNode;
* return nullptr if aCurrentNode has no children.
@ -927,12 +1041,14 @@ public:
static nsresult GetStartNodeAndOffset(Selection* aSelection,
nsINode** aStartContainer,
int32_t* aStartOffset);
static EditorRawDOMPoint GetStartPoint(Selection* aSelection);
static nsresult GetEndNodeAndOffset(Selection* aSelection,
nsIDOMNode** outEndNode,
int32_t* outEndOffset);
static nsresult GetEndNodeAndOffset(Selection* aSelection,
nsINode** aEndContainer,
int32_t* aEndOffset);
static EditorRawDOMPoint GetEndPoint(Selection* aSelection);
static nsresult GetEndChildNode(Selection* aSelection,
nsIContent** aEndNode);
@ -1258,22 +1374,11 @@ public:
* FindBetterInsertionPoint() tries to look for better insertion point which
* is typically the nearest text node and offset in it.
*
* @param aNode in/out param, on input set to the node to use to start the search,
* on output set to the node found as the better insertion point.
* @param aOffset in/out param, on input set to the offset to use to start the
* search, on putput set to the offset found as the better insertion
* point.
* @param aSelChild in/out param, on input, can be set to nullptr if the caller
* doesn't want to pass this in, or set to a pointer to an nsCOMPtr
* pointing to the child at the input node and offset, and on output
* the method will make it point to the child at the output node and
* offset returned in aNode and aOffset.
* @param aPoint Insertion point which the callers found.
* @return Better insertion point if there is. If not returns
* same point as aPoint.
*/
void FindBetterInsertionPoint(nsCOMPtr<nsIDOMNode>& aNode,
int32_t& aOffset);
void FindBetterInsertionPoint(nsCOMPtr<nsINode>& aNode,
int32_t& aOffset,
nsCOMPtr<nsIContent>* aSelChild);
EditorRawDOMPoint FindBetterInsertionPoint(const EditorRawDOMPoint& aPoint);
/**
* HideCaret() hides caret with nsCaret::AddForceHide() or may show carent

Просмотреть файл

@ -84,6 +84,19 @@ public:
{
}
EditorDOMPointBase<nsINode*, nsIContent*>
AsRaw() const
{
return EditorDOMPointBase<nsINode*, nsIContent*>(*this);
}
template<typename A, typename B>
EditorDOMPointBase& operator=(const EditorDOMPointBase<A, B>& aOther)
{
RangeBoundaryBase<ParentType, RefType>::operator=(aOther);
return *this;
}
private:
static nsIContent* GetRef(nsINode* aContainerNode, nsIContent* aPointedNode)
{

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -410,11 +410,22 @@ protected:
void CheckInterlinePosition(Selection& aSelection);
nsresult AdjustSelection(Selection* aSelection,
nsIEditor::EDirection aAction);
nsresult FindNearSelectableNode(nsINode* aSelNode,
int32_t aSelOffset,
nsINode* aChildAtOffset,
nsIEditor::EDirection& aDirection,
nsCOMPtr<nsIContent>* outSelectableNode);
/**
* FindNearEditableNode() tries to find an editable node near aPoint.
*
* @param aPoint The DOM point where to start to search from.
* @param aDirection If nsIEditor::ePrevious is set, this searches an
* editable node from next nodes. Otherwise, from
* previous nodes.
* @return If found, returns non-nullptr. Otherwise, nullptr.
* Note that if found node is in different table element,
* this returns nullptr.
* And also if aDirection is not nsIEditor::ePrevious,
* the result may be the node pointed by aPoint.
*/
nsIContent* FindNearEditableNode(const EditorRawDOMPoint& aPoint,
nsIEditor::EDirection aDirection);
/**
* Returns true if aNode1 or aNode2 or both is the descendant of some type of
* table element, but their nearest table element ancestors differ. "Table

Просмотреть файл

@ -876,7 +876,7 @@ HTMLEditor::IsVisibleBRElement(nsINode* aNode)
return false;
}
// Check if there is a later node in block after br
nsCOMPtr<nsINode> nextNode = GetNextHTMLNode(aNode, true);
nsCOMPtr<nsINode> nextNode = GetNextEditableHTMLNodeInBlock(*aNode);
if (nextNode && TextEditUtils::IsBreak(nextNode)) {
return true;
}
@ -895,7 +895,7 @@ HTMLEditor::IsVisibleBRElement(nsINode* aNode)
// If there's an inline node after this one that's not a break, and also a
// prior break, this break must be visible.
nsCOMPtr<nsINode> priorNode = GetPriorHTMLNode(aNode, true);
nsCOMPtr<nsINode> priorNode = GetPreviousEditableHTMLNodeInBlock(*aNode);
if (priorNode && TextEditUtils::IsBreak(priorNode)) {
return true;
}
@ -3186,20 +3186,22 @@ HTMLEditor::DeleteText(nsGenericDOMDataNode& aCharData,
}
nsresult
HTMLEditor::InsertTextImpl(const nsAString& aStringToInsert,
nsCOMPtr<nsINode>* aInOutNode,
nsCOMPtr<nsIContent>* aInOutChildAtOffset,
int32_t* aInOutOffset,
nsIDocument* aDoc)
HTMLEditor::InsertTextImpl(nsIDocument& aDocument,
const nsAString& aStringToInsert,
const EditorRawDOMPoint& aPointToInsert,
EditorRawDOMPoint* aPointAfterInsertedString)
{
if (NS_WARN_IF(!aPointToInsert.IsSet())) {
return NS_ERROR_INVALID_ARG;
}
// Do nothing if the node is read-only
if (!IsModifiableNode(*aInOutNode)) {
if (!IsModifiableNode(aPointToInsert.Container())) {
return NS_ERROR_FAILURE;
}
return EditorBase::InsertTextImpl(aStringToInsert, aInOutNode,
aInOutChildAtOffset,
aInOutOffset, aDoc);
return EditorBase::InsertTextImpl(aDocument, aStringToInsert, aPointToInsert,
aPointAfterInsertedString);
}
void
@ -3865,76 +3867,48 @@ HTMLEditor::GetNextHTMLSibling(nsINode* aNode)
return node;
}
/**
* GetPriorHTMLNode() returns the previous editable leaf node, if there is
* one within the <body>.
*/
nsIContent*
HTMLEditor::GetPriorHTMLNode(nsINode* aNode,
bool aNoBlockCrossing)
HTMLEditor::GetPreviousEditableHTMLNodeInternal(nsINode& aNode,
bool aNoBlockCrossing)
{
MOZ_ASSERT(aNode);
if (!GetActiveEditingHost()) {
return nullptr;
}
return GetPriorNode(aNode, true, aNoBlockCrossing);
return aNoBlockCrossing ? GetPreviousEditableNodeInBlock(aNode) :
GetPreviousEditableNode(aNode);
}
/**
* GetPriorHTMLNode() is same as above but takes {parent,offset} instead of
* node.
*/
nsIContent*
HTMLEditor::GetPriorHTMLNode(nsINode* aParent,
int32_t aOffset,
nsINode* aChildAtOffset,
bool aNoBlockCrossing)
HTMLEditor::GetPreviousEditableHTMLNodeInternal(const EditorRawDOMPoint& aPoint,
bool aNoBlockCrossing)
{
MOZ_ASSERT(aParent);
if (!GetActiveEditingHost()) {
return nullptr;
}
return GetPriorNode(aParent, aOffset, aChildAtOffset, true, aNoBlockCrossing);
return aNoBlockCrossing ? GetPreviousEditableNodeInBlock(aPoint) :
GetPreviousEditableNode(aPoint);
}
/**
* GetNextHTMLNode() returns the next editable leaf node, if there is
* one within the <body>.
*/
nsIContent*
HTMLEditor::GetNextHTMLNode(nsINode* aNode,
bool aNoBlockCrossing)
HTMLEditor::GetNextEditableHTMLNodeInternal(nsINode& aNode,
bool aNoBlockCrossing)
{
MOZ_ASSERT(aNode);
nsIContent* result = GetNextNode(aNode, true, aNoBlockCrossing);
if (result && !IsDescendantOfEditorRoot(result)) {
if (!GetActiveEditingHost()) {
return nullptr;
}
return result;
return aNoBlockCrossing ? GetNextEditableNodeInBlock(aNode) :
GetNextEditableNode(aNode);
}
/**
* GetNextHTMLNode() is same as above but takes {parent,offset} instead of node.
*/
nsIContent*
HTMLEditor::GetNextHTMLNode(nsINode* aParent,
int32_t aOffset,
nsINode* aChildAtOffset,
bool aNoBlockCrossing)
HTMLEditor::GetNextEditableHTMLNodeInternal(const EditorRawDOMPoint& aPoint,
bool aNoBlockCrossing)
{
nsIContent* content = GetNextNode(aParent, aOffset, aChildAtOffset,
true, aNoBlockCrossing);
if (content && !IsDescendantOfEditorRoot(content)) {
if (!GetActiveEditingHost()) {
return nullptr;
}
return content;
return aNoBlockCrossing ? GetNextEditableNodeInBlock(aPoint) :
GetNextEditableNode(aPoint);
}
bool
@ -3990,7 +3964,7 @@ HTMLEditor::GetFirstEditableLeaf(nsINode& aNode)
{
nsCOMPtr<nsIContent> child = GetLeftmostChild(&aNode);
while (child && (!IsEditable(child) || child->HasChildren())) {
child = GetNextHTMLNode(child);
child = GetNextEditableHTMLNode(*child);
// Only accept nodes that are descendants of aNode
if (!aNode.Contains(child)) {
@ -4006,7 +3980,7 @@ HTMLEditor::GetLastEditableLeaf(nsINode& aNode)
{
nsCOMPtr<nsIContent> child = GetRightmostChild(&aNode, false);
while (child && (!IsEditable(child) || child->HasChildren())) {
child = GetPriorHTMLNode(child);
child = GetPreviousEditableHTMLNode(*child);
// Only accept nodes that are descendants of aNode
if (!aNode.Contains(child)) {
@ -4521,7 +4495,7 @@ HTMLEditor::CopyLastEditableChildStyles(nsINode* aPreviousBlock,
tmp = GetLastEditableChild(*child);
}
while (child && TextEditUtils::IsBreak(child)) {
child = GetPriorHTMLNode(child);
child = GetPreviousEditableHTMLNode(*child);
}
nsCOMPtr<Element> newStyles, deepestStyle;
nsCOMPtr<nsINode> childNode = child;

Просмотреть файл

@ -321,11 +321,12 @@ public:
NS_IMETHOD DeleteNode(nsIDOMNode* aNode) override;
nsresult DeleteText(nsGenericDOMDataNode& aTextNode, uint32_t aOffset,
uint32_t aLength);
virtual nsresult InsertTextImpl(const nsAString& aStringToInsert,
nsCOMPtr<nsINode>* aInOutNode,
nsCOMPtr<nsIContent>* aInOutChildAtOffset,
int32_t* aInOutOffset,
nsIDocument* aDoc) override;
virtual nsresult
InsertTextImpl(nsIDocument& aDocument,
const nsAString& aStringToInsert,
const EditorRawDOMPoint& aPointToInsert,
EditorRawDOMPoint* aPointAfterInsertedString =
nullptr) override;
NS_IMETHOD_(bool) IsModifiableNode(nsIDOMNode* aNode) override;
virtual bool IsModifiableNode(nsINode* aNode) override;
@ -776,15 +777,76 @@ protected:
nsIContent* GetNextHTMLSibling(nsINode* aNode);
nsIContent* GetPriorHTMLNode(nsINode* aNode, bool aNoBlockCrossing = false);
nsIContent* GetPriorHTMLNode(nsINode* aParent, int32_t aOffset,
nsINode* aChildAtOffset,
bool aNoBlockCrossing = false);
/**
* GetPreviousEditableHTMLNode*() methods are similar to
* EditorBase::GetPreviousEditableNode() but this won't return nodes outside
* active editing host.
*/
nsIContent* GetPreviousEditableHTMLNode(nsINode& aNode)
{
return GetPreviousEditableHTMLNodeInternal(aNode, false);
}
nsIContent* GetPreviousEditableHTMLNodeInBlock(nsINode& aNode)
{
return GetPreviousEditableHTMLNodeInternal(aNode, true);
}
nsIContent* GetPreviousEditableHTMLNode(const EditorRawDOMPoint& aPoint)
{
return GetPreviousEditableHTMLNodeInternal(aPoint, false);
}
nsIContent* GetPreviousEditableHTMLNodeInBlock(
const EditorRawDOMPoint& aPoint)
{
return GetPreviousEditableHTMLNodeInternal(aPoint, true);
}
nsIContent* GetNextHTMLNode(nsINode* aNode, bool aNoBlockCrossing = false);
nsIContent* GetNextHTMLNode(nsINode* aParent, int32_t aOffset,
nsINode* aChildAtOffset,
bool aNoBlockCrossing = false);
/**
* GetPreviousEditableHTMLNodeInternal() methods are common implementation
* of above methods. Please don't use this method directly.
*/
nsIContent* GetPreviousEditableHTMLNodeInternal(nsINode& aNode,
bool aNoBlockCrossing);
nsIContent* GetPreviousEditableHTMLNodeInternal(
const EditorRawDOMPoint& aPoint,
bool aNoBlockCrossing);
/**
* GetNextEditableHTMLNode*() methods are similar to
* EditorBase::GetNextEditableNode() but this won't return nodes outside
* active editing host.
*
* Note that same as EditorBaseGetTextEditableNode(), methods which take
* |const EditorRawDOMPoint&| start to search from the node pointed by it.
* On the other hand, methods which take |nsINode&| start to search from
* next node of aNode.
*/
nsIContent* GetNextEditableHTMLNode(nsINode& aNode)
{
return GetNextEditableHTMLNodeInternal(aNode, false);
}
nsIContent* GetNextEditableHTMLNodeInBlock(nsINode& aNode)
{
return GetNextEditableHTMLNodeInternal(aNode, true);
}
nsIContent* GetNextEditableHTMLNode(const EditorRawDOMPoint& aPoint)
{
return GetNextEditableHTMLNodeInternal(aPoint, false);
}
nsIContent* GetNextEditableHTMLNodeInBlock(
const EditorRawDOMPoint& aPoint)
{
return GetNextEditableHTMLNodeInternal(aPoint, true);
}
/**
* GetNextEditableHTMLNodeInternal() methods are common implementation
* of above methods. Please don't use this method directly.
*/
nsIContent* GetNextEditableHTMLNodeInternal(nsINode& aNode,
bool aNoBlockCrossing);
nsIContent* GetNextEditableHTMLNodeInternal(
const EditorRawDOMPoint& aPoint,
bool aNoBlockCrossing);
bool IsFirstEditableChild(nsINode* aNode);
bool IsLastEditableChild(nsINode* aNode);

Просмотреть файл

@ -729,16 +729,16 @@ TextEditRules::WillInsertText(EditAction aAction,
// get the (collapsed) selection location
NS_ENSURE_STATE(aSelection->GetRangeAt(0));
nsCOMPtr<nsINode> selNode = aSelection->GetRangeAt(0)->GetStartContainer();
nsCOMPtr<nsIContent> selChild =
aSelection->GetRangeAt(0)->GetChildAtStartOffset();
int32_t selOffset = aSelection->GetRangeAt(0)->StartOffset();
NS_ENSURE_STATE(selNode);
EditorRawDOMPoint atStartOfSelection(aSelection->GetRangeAt(0)->StartRef());
if (NS_WARN_IF(!atStartOfSelection.IsSetAndValid())) {
return NS_ERROR_FAILURE;
}
// don't put text in places that can't have it
NS_ENSURE_STATE(mTextEditor);
if (!EditorBase::IsTextNode(selNode) &&
!mTextEditor->CanContainTag(*selNode, *nsGkAtoms::textTagName)) {
if (!EditorBase::IsTextNode(atStartOfSelection.Container()) &&
!mTextEditor->CanContainTag(*atStartOfSelection.Container(),
*nsGkAtoms::textTagName)) {
return NS_ERROR_FAILURE;
}
@ -750,41 +750,46 @@ TextEditRules::WillInsertText(EditAction aAction,
if (aAction == EditAction::insertIMEText) {
NS_ENSURE_STATE(mTextEditor);
// Find better insertion point to insert text.
mTextEditor->FindBetterInsertionPoint(selNode, selOffset,
address_of(selChild));
EditorRawDOMPoint betterInsertionPoint =
mTextEditor->FindBetterInsertionPoint(atStartOfSelection);
// If there is one or more IME selections, its minimum offset should be
// the insertion point.
int32_t IMESelectionOffset =
mTextEditor->GetIMESelectionStartOffsetIn(selNode);
mTextEditor->GetIMESelectionStartOffsetIn(
betterInsertionPoint.Container());
if (IMESelectionOffset >= 0) {
selOffset = IMESelectionOffset;
betterInsertionPoint.Set(betterInsertionPoint.Container(),
IMESelectionOffset);
}
rv = mTextEditor->InsertTextImpl(*outString, address_of(selNode),
address_of(selChild), &selOffset, doc);
rv = mTextEditor->InsertTextImpl(*doc, *outString, betterInsertionPoint);
NS_ENSURE_SUCCESS(rv, rv);
} else {
// aAction == EditAction::insertText; find where we are
nsCOMPtr<nsINode> curNode = selNode;
int32_t curOffset = selOffset;
// aAction == EditAction::insertText
// don't change my selection in subtransactions
NS_ENSURE_STATE(mTextEditor);
AutoTransactionsConserveSelection dontChangeMySelection(mTextEditor);
rv = mTextEditor->InsertTextImpl(*outString, address_of(curNode),
address_of(selChild), &curOffset, doc);
EditorRawDOMPoint pointAfterStringInserted;
rv = mTextEditor->InsertTextImpl(*doc, *outString, atStartOfSelection,
&pointAfterStringInserted);
NS_ENSURE_SUCCESS(rv, rv);
if (curNode) {
if (pointAfterStringInserted.IsSet()) {
// Make the caret attach to the inserted text, unless this text ends with a LF,
// in which case make the caret attach to the next line.
bool endsWithLF =
!outString->IsEmpty() && outString->Last() == nsCRT::LF;
aSelection->SetInterlinePosition(endsWithLF);
MOZ_ASSERT(!selChild,
"After inserting text into a text node, selChild should be nullptr");
aSelection->Collapse(curNode, curOffset);
MOZ_ASSERT(!pointAfterStringInserted.GetChildAtOffset(),
"After inserting text into a text node, pointAfterStringInserted."
"GetChildAtOffset() should be nullptr");
ErrorResult error;
aSelection->Collapse(pointAfterStringInserted, error);
if (error.Failed()) {
NS_WARNING("Failed to collapse selection after inserting string");
}
}
}
ASSERT_PASSWORD_LENGTHS_EQUAL()

Просмотреть файл

@ -532,63 +532,89 @@ TextEditor::ExtendSelectionForDelete(Selection* aSelection,
GetSelectionController(getter_AddRefs(selCont));
NS_ENSURE_TRUE(selCont, NS_ERROR_NO_INTERFACE);
nsresult rv;
switch (*aAction) {
case eNextWord:
rv = selCont->WordExtendForDelete(true);
case eNextWord: {
nsresult rv = selCont->WordExtendForDelete(true);
// DeleteSelectionImpl doesn't handle these actions
// because it's inside batching, so don't confuse it:
*aAction = eNone;
break;
case ePreviousWord:
rv = selCont->WordExtendForDelete(false);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
case ePreviousWord: {
nsresult rv = selCont->WordExtendForDelete(false);
*aAction = eNone;
break;
case eNext:
rv = selCont->CharacterExtendForDelete();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
case eNext: {
nsresult rv = selCont->CharacterExtendForDelete();
// Don't set aAction to eNone (see Bug 502259)
break;
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
case ePrevious: {
// Only extend the selection where the selection is after a UTF-16
// surrogate pair or a variation selector.
// For other cases we don't want to do that, in order
// to make sure that pressing backspace will only delete the last
// typed character.
nsCOMPtr<nsINode> node;
int32_t offset;
rv = GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
EditorRawDOMPoint atStartOfSelection =
EditorBase::GetStartPoint(aSelection);
if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
return NS_ERROR_FAILURE;
}
// node might be anonymous DIV, so we find better text node
FindBetterInsertionPoint(node, offset, nullptr);
EditorRawDOMPoint insertionPoint =
FindBetterInsertionPoint(atStartOfSelection);
if (IsTextNode(node)) {
const nsTextFragment* data = node->GetAsText()->GetText();
if (IsTextNode(insertionPoint.Container())) {
const nsTextFragment* data =
insertionPoint.Container()->GetAsText()->GetText();
uint32_t offset = insertionPoint.Offset();
if ((offset > 1 &&
NS_IS_LOW_SURROGATE(data->CharAt(offset - 1)) &&
NS_IS_HIGH_SURROGATE(data->CharAt(offset - 2))) ||
(offset > 0 &&
gfxFontUtils::IsVarSelector(data->CharAt(offset - 1)))) {
rv = selCont->CharacterExtendForBackspace();
nsresult rv = selCont->CharacterExtendForBackspace();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
}
break;
return NS_OK;
}
case eToBeginningOfLine:
selCont->IntraLineMove(true, false); // try to move to end
rv = selCont->IntraLineMove(false, true); // select to beginning
case eToBeginningOfLine: {
// Try to move to end
selCont->IntraLineMove(true, false);
// Select to beginning
nsresult rv = selCont->IntraLineMove(false, true);
*aAction = eNone;
break;
case eToEndOfLine:
rv = selCont->IntraLineMove(true, true);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
case eToEndOfLine: {
nsresult rv = selCont->IntraLineMove(true, true);
*aAction = eNext;
break;
default: // avoid several compiler warnings
rv = NS_OK;
break;
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
// For avoiding several compiler warnings
default:
return NS_OK;
}
return rv;
}
return NS_OK;
}
@ -708,19 +734,28 @@ TextEditor::InsertLineBreak()
TextRulesInfo ruleInfo(EditAction::insertBreak);
ruleInfo.maxLength = mMaxTextLength;
bool cancel, handled;
// XXX DidDoAction() won't be called when this returns error. Perhaps,
// we should move the code between WillDoAction() and DidDoAction()
// to a new method and guarantee that DidDoAction() is always called
// after WillDoAction().
nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
NS_ENSURE_SUCCESS(rv, rv);
if (!cancel && !handled) {
// get the (collapsed) selection location
NS_ENSURE_STATE(selection->GetRangeAt(0));
nsCOMPtr<nsINode> selNode = selection->GetRangeAt(0)->GetStartContainer();
nsCOMPtr<nsIContent> selChild = selection->GetRangeAt(0)->GetChildAtStartOffset();
int32_t selOffset = selection->GetRangeAt(0)->StartOffset();
NS_ENSURE_STATE(selNode);
nsRange* firstRange = selection->GetRangeAt(0);
if (NS_WARN_IF(!firstRange)) {
return NS_ERROR_FAILURE;
}
EditorRawDOMPoint pointToInsert(firstRange->StartRef());
if (NS_WARN_IF(!pointToInsert.IsSet())) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(pointToInsert.IsSetAndValid());
// don't put text in places that can't have it
if (!IsTextNode(selNode) && !CanContainTag(*selNode,
*nsGkAtoms::textTagName)) {
if (!IsTextNode(pointToInsert.Container()) &&
!CanContainTag(*pointToInsert.Container(), *nsGkAtoms::textTagName)) {
return NS_ERROR_FAILURE;
}
@ -732,29 +767,26 @@ TextEditor::InsertLineBreak()
AutoTransactionsConserveSelection dontChangeMySelection(this);
// insert a linefeed character
rv = InsertTextImpl(NS_LITERAL_STRING("\n"), address_of(selNode),
address_of(selChild), &selOffset, doc);
if (!selNode) {
EditorRawDOMPoint pointAfterInsertedLineBreak;
rv = InsertTextImpl(*doc, NS_LITERAL_STRING("\n"), pointToInsert,
&pointAfterInsertedLineBreak);
if (NS_WARN_IF(!pointAfterInsertedLineBreak.IsSet())) {
rv = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
}
if (NS_SUCCEEDED(rv)) {
// set the selection to the correct location
MOZ_ASSERT(!selChild,
"After inserting text into a text node, selChild should be nullptr");
rv = selection->Collapse(EditorRawDOMPoint(selNode, selOffset));
MOZ_ASSERT(!pointAfterInsertedLineBreak.GetChildAtOffset(),
"After inserting text into a text node, pointAfterInsertedLineBreak."
"GetChildAtOffset() should be nullptr");
rv = selection->Collapse(pointAfterInsertedLineBreak);
if (NS_SUCCEEDED(rv)) {
// see if we're at the end of the editor range
nsCOMPtr<nsIDOMNode> endNode;
int32_t endOffset;
rv = GetEndNodeAndOffset(selection,
getter_AddRefs(endNode), &endOffset);
if (NS_SUCCEEDED(rv) &&
endNode == GetAsDOMNode(selNode) && endOffset == selOffset) {
// SetInterlinePosition(true) means we want the caret to stick to the content on the "right".
// We want the caret to stick to whatever is past the break. This is
// because the break is on the same line we were on, but the next content
// will be on the following line.
EditorRawDOMPoint endPoint = GetEndPoint(selection);
if (endPoint == pointAfterInsertedLineBreak) {
// SetInterlinePosition(true) means we want the caret to stick to the
// content on the "right". We want the caret to stick to whatever is
// past the break. This is because the break is on the same line we
// were on, but the next content will be on the following line.
selection->SetInterlinePosition(true);
}
}

Просмотреть файл

@ -233,11 +233,10 @@ WSRunObject::InsertBreak(nsCOMPtr<nsINode>* aInOutParent,
}
nsresult
WSRunObject::InsertText(const nsAString& aStringToInsert,
nsCOMPtr<nsINode>* aInOutParent,
nsCOMPtr<nsIContent>* aInOutChildAtOffset,
int32_t* aInOutOffset,
nsIDocument* aDoc)
WSRunObject::InsertText(nsIDocument& aDocument,
const nsAString& aStringToInsert,
const EditorRawDOMPoint& aPointToInsert,
EditorRawDOMPoint* aPointAfterInsertedString)
{
// MOOSE: for now, we always assume non-PRE formatting. Fix this later.
// meanwhile, the pre case is handled in WillInsertText in
@ -247,25 +246,33 @@ WSRunObject::InsertText(const nsAString& aStringToInsert,
// is very slow. Will need to replace edit rules impl with a more efficient
// text sink here that does the minimal amount of searching/replacing/copying
NS_ENSURE_TRUE(aInOutParent && aInOutOffset && aDoc, NS_ERROR_NULL_POINTER);
if (NS_WARN_IF(!aPointToInsert.IsSet())) {
return NS_ERROR_INVALID_ARG;
}
MOZ_ASSERT(aPointToInsert.IsSet());
if (aStringToInsert.IsEmpty()) {
if (aPointAfterInsertedString) {
*aPointAfterInsertedString = aPointToInsert;
}
return NS_OK;
}
EditorDOMPoint pointToInsert(aPointToInsert);
nsAutoString theString(aStringToInsert);
WSFragment *beforeRun, *afterRun;
FindRun(*aInOutParent, *aInOutOffset, &beforeRun, false);
FindRun(*aInOutParent, *aInOutOffset, &afterRun, true);
FindRun(pointToInsert.Container(), pointToInsert.Offset(),
&beforeRun, false);
FindRun(pointToInsert.Container(), pointToInsert.Offset(),
&afterRun, true);
{
// Some scoping for AutoTrackDOMPoint. This will track our insertion
// point while we tweak any surrounding whitespace
AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, aInOutParent,
aInOutOffset);
AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, &pointToInsert);
bool maybeModified = false;
// Handle any changes needed to ws run after inserted text
if (!afterRun || afterRun->mType & WSType::trailingWS) {
// Don't need to do anything. Just insert text. ws won't change.
@ -273,16 +280,15 @@ WSRunObject::InsertText(const nsAString& aStringToInsert,
// Delete the leading ws that is after insertion point, because it
// would become significant after text inserted.
nsresult rv =
DeleteChars(*aInOutParent, *aInOutOffset, afterRun->mEndNode,
afterRun->mEndOffset);
DeleteChars(pointToInsert.Container(), pointToInsert.Offset(),
afterRun->mEndNode, afterRun->mEndOffset);
NS_ENSURE_SUCCESS(rv, rv);
maybeModified = true;
} else if (afterRun->mType == WSType::normalWS) {
// Try to change an nbsp to a space, if possible, just to prevent nbsp
// proliferation
nsresult rv = CheckLeadingNBSP(afterRun, *aInOutParent, *aInOutOffset);
nsresult rv = CheckLeadingNBSP(afterRun, pointToInsert.Container(),
pointToInsert.Offset());
NS_ENSURE_SUCCESS(rv, rv);
maybeModified = true;
}
// Handle any changes needed to ws run before inserted text
@ -293,30 +299,17 @@ WSRunObject::InsertText(const nsAString& aStringToInsert,
// it would become significant after text inserted.
nsresult rv =
DeleteChars(beforeRun->mStartNode, beforeRun->mStartOffset,
*aInOutParent, *aInOutOffset);
pointToInsert.Container(), pointToInsert.Offset());
NS_ENSURE_SUCCESS(rv, rv);
maybeModified = true;
} else if (beforeRun->mType == WSType::normalWS) {
// Try to change an nbsp to a space, if possible, just to prevent nbsp
// proliferation
nsresult rv = CheckTrailingNBSP(beforeRun, *aInOutParent, *aInOutOffset);
nsresult rv = CheckTrailingNBSP(beforeRun, pointToInsert.Container(),
pointToInsert.Offset());
NS_ENSURE_SUCCESS(rv, rv);
maybeModified = true;
}
// The child node may be changed. So, even though getting child at offset
// is expensive, we need to do it here.
if (maybeModified) {
if ((*aInOutParent)->HasChildren()) {
if (*aInOutOffset == 0) {
*aInOutChildAtOffset = (*aInOutParent)->GetFirstChild();
} else {
*aInOutChildAtOffset = (*aInOutParent)->GetChildAt(*aInOutOffset);
}
} else {
*aInOutChildAtOffset = nullptr;
}
}
// After this block, pointToInsert is modified by AutoTrackDOMPoint.
}
// Next up, tweak head and tail of string as needed. First the head: there
@ -329,7 +322,8 @@ WSRunObject::InsertText(const nsAString& aStringToInsert,
if (beforeRun->mType & WSType::leadingWS) {
theString.SetCharAt(nbsp, 0);
} else if (beforeRun->mType & WSType::normalWS) {
WSPoint wspoint = GetCharBefore(*aInOutParent, *aInOutOffset);
WSPoint wspoint =
GetCharBefore(pointToInsert.Container(), pointToInsert.Offset());
if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
theString.SetCharAt(nbsp, 0);
}
@ -348,7 +342,8 @@ WSRunObject::InsertText(const nsAString& aStringToInsert,
if (afterRun->mType & WSType::trailingWS) {
theString.SetCharAt(nbsp, lastCharIndex);
} else if (afterRun->mType & WSType::normalWS) {
WSPoint wspoint = GetCharAfter(*aInOutParent, *aInOutOffset);
WSPoint wspoint =
GetCharAfter(pointToInsert.Container(), pointToInsert.Offset());
if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
theString.SetCharAt(nbsp, lastCharIndex);
}
@ -377,8 +372,12 @@ WSRunObject::InsertText(const nsAString& aStringToInsert,
}
// Ready, aim, fire!
mHTMLEditor->InsertTextImpl(theString, aInOutParent, aInOutChildAtOffset,
aInOutOffset, aDoc);
nsresult rv =
mHTMLEditor->InsertTextImpl(aDocument, theString, pointToInsert.AsRaw(),
aPointAfterInsertedString);
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_OK;
}
return NS_OK;
}

Просмотреть файл

@ -219,14 +219,31 @@ public:
int32_t* aInOutOffset,
nsIEditor::EDirection aSelect);
// InsertText inserts a string at {aInOutParent,aInOutOffset} and makes any
// needed adjustments to ws around that point. Example of fixup:
// trailingws before {aInOutParent,aInOutOffset} needs to be removed.
nsresult InsertText(const nsAString& aStringToInsert,
nsCOMPtr<nsINode>* aInOutNode,
nsCOMPtr<nsIContent>* aInOutChildAtOffset,
int32_t* aInOutOffset,
nsIDocument* aDoc);
/**
* InsertTextImpl() inserts aStringToInsert to aPointToInsert and makes any
* needed adjustments to white spaces around that point. E.g., trailing white
* spaces before aPointToInsert needs to be removed.
* This calls EditorBase::InsertTextImpl() after adjusting white spaces.
* So, please refer the method's explanation to know what this method exactly
* does.
*
* @param aDocument The document of this editor.
* @param aStringToInsert The string to insert.
* @param aPointToInser The point to insert aStringToInsert.
* Must be valid DOM point.
* @param aPointAfterInsertedString
* The point after inserted aStringToInsert.
* So, when this method actually inserts string,
* this is set to a point in the text node.
* Otherwise, this may be set to aPointToInsert.
* @return When this succeeds to insert the string or
* does nothing during composition, returns NS_OK.
* Otherwise, an error code.
*/
nsresult InsertText(nsIDocument& aDocument,
const nsAString& aStringToInsert,
const EditorRawDOMPoint& aPointToInsert,
EditorRawDOMPoint* aPointAfterInsertedString = nullptr);
// DeleteWSBackward deletes a single visible piece of ws before the ws
// point (the point to create the wsRunObject, passed to its constructor).

Просмотреть файл

@ -0,0 +1,17 @@
<script>
function jsfuzzer() {
var var00043 = window.getSelection();
htmlvar00018.addEventListener("DOMNodeRemoved", eh1);
var00043.setPosition(htmlvar00016);
document.execCommand("insertText", false, "1");
}
function eh1() {
document.execCommand("insertHorizontalRule", false);
document.execCommand("justifyCenter", false);
}
</script>
<body onload=jsfuzzer()>
<li contenteditable="true">
<shadow id="htmlvar00016">
<ins id="htmlvar00018">
<option>

Просмотреть файл

@ -94,3 +94,4 @@ load 1402904.html
load 1405747.html
load 1408170.html
load 1414581.html
load 1415231.html

Просмотреть файл

@ -175,4 +175,4 @@ Troubleshooting tips:
-------------------------------------------------------------------------------
The version of WebRender currently in the tree is:
34f1e8ed19a19cb950deef89ee31c1cf3d442d22
f58ed651b47f47382b63dd2bce6e4ed10ee18c78

Просмотреть файл

@ -20,7 +20,12 @@ namespace mozilla {
struct RoundedRect {
typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
RoundedRect(gfxRect &aRect, RectCornerRadii &aCorners) : rect(aRect), corners(aCorners) { }
RoundedRect(const gfxRect& aRect, const RectCornerRadii& aCorners)
: rect(aRect)
, corners(aCorners)
{
}
void Deflate(gfxFloat aTopWidth, gfxFloat aBottomWidth, gfxFloat aLeftWidth, gfxFloat aRightWidth) {
// deflate the internal rect
rect.x += aLeftWidth;

Просмотреть файл

@ -93,7 +93,7 @@ protected:
VRDisplayInfo mDisplayInfo;
nsTArray<RefPtr<VRLayerParent>> mLayers;
nsTArray<VRLayerParent *> mLayers;
// Weak reference to mLayers entries are cleared in
// VRLayerParent destructor

Просмотреть файл

@ -37,7 +37,7 @@ enum class OculusControllerAxisType : uint16_t {
class VROculusSession
{
NS_INLINE_DECL_REFCOUNTING(VROculusSession);
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VROculusSession);
friend class VRDisplayOculus;
public:
VROculusSession();

Просмотреть файл

@ -370,21 +370,24 @@ VRDisplayPuppet::SubmitFrame(ID3D11Texture2D* aSource,
// access the image library. So, we have to convert the RAW image data
// to a base64 string and forward it to let the content process to
// do the image conversion.
char* srcData = static_cast<char*>(mapInfo.pData);
const char* srcData = static_cast<const char*>(mapInfo.pData);
VRSubmitFrameResultInfo result;
result.mFormat = SurfaceFormat::B8G8R8A8;
// If the original texture size is not pow of 2, CopyResource() will add padding,
// so the size is adjusted. We have to get the correct size by (mapInfo.RowPitch /
// the format size).
result.mWidth = mapInfo.RowPitch / 4;
result.mWidth = desc.Width;
result.mHeight = desc.Height;
result.mFrameNum = mDisplayInfo.mFrameId;
nsCString rawString(Substring(srcData, mapInfo.RowPitch * desc.Height));
// If the original texture size is not pow of 2, the data will not be tightly strided.
// We have to copy the pixels by rows.
nsCString rawString;
for (uint32_t i = 0; i < desc.Height; i++) {
rawString += Substring(srcData + i * mapInfo.RowPitch,
desc.Width * 4);
}
mContext->Unmap(mappedTexture, 0);
if (Base64Encode(rawString, result.mBase64Image) != NS_OK) {
MOZ_ASSERT(false, "Failed to encode base64 images.");
}
mContext->Unmap(mappedTexture, 0);
// Dispatch the base64 encoded string to the DOM side. Then, it will be decoded
// and convert to a PNG image there.
MessageLoop* loop = VRListenerThreadHolder::Loop();

Просмотреть файл

@ -17,7 +17,7 @@ query = []
app_units = "0.5.6"
bincode = "0.9"
byteorder = "1.0"
euclid = "0.15.2"
euclid = "0.15.5"
fxhash = "0.2.1"
gleam = "0.4.8"
lazy_static = "0.2"

Просмотреть файл

@ -293,12 +293,44 @@ and output `text_color.a * mask` from our fragment shader.
But then there's still the problem that the first summand of the computation for `result.r` uses
`text_color.r * mask.r` and the second summand uses `text_color.a * mask.r`.
There's no way around it, we have to use two passes.
(Actually, there is a way around it, but it requires the use of `glBlendColor`, which we want to avoid because
we'd have to use different draw calls for different text colors, or it requires "dual source blending" which is
not supported everywhere.)
There are multiple ways to deal with this. They are:
Here's how we can express the subpixel text blend function with two passes:
1. Making use of `glBlendColor` and the `GL_CONSTANT_COLOR` blend func.
2. Using a two-pass method.
3. Using "dual source blending".
Let's look at them in order.
#### 1. Subpixel text blending in OpenGL using `glBlendColor`
In this approach we return `text_color.a * mask` from the shader.
Then we set the blend color to `text_color / text_color.a` and use `GL_CONSTANT_COLOR` as the source blendfunc.
This results in the following blend equation:
```
result.r = (text_color.r / text_color.a) * oFragColor.r + (1 - oFragColor.r) * dest.r;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ ^^^^^^^^^^^^^^^^^
| | |
+--gl::CONSTANT_COLOR | +-- gl::ONE_MINUS_SRC_COLOR
|
+-- gl::FUNC_ADD
= (text_color.r / text_color.a) * (text_color.a * mask.r) + (1 - (text_color.a * mask.r)) * dest.r
= text_color.r * mask.r + (1 - text_color.a * mask.r) * dest.r
```
At the very beginning of this document, we defined `text_color` as the *premultiplied* text color.
So instead of actually doing the calculation `text_color.r / text_color.a` when specifying the blend color,
we really just want to use the *unpremultiplied* text color in that place.
That's usually the representation we start with anyway.
#### 2. Two-pass subpixel blending in OpenGL
The `glBlendColor` method has the disadvantage that the text color is part of the OpenGL state.
So if we want to draw text with different colors, we have two use separate batches / draw calls
to draw the differently-colored parts of text.
Alternatively, we can use a two-pass method which avoids the need to use the `GL_CONSTANT_COLOR` blend func:
- The first pass outputs `text_color.a * mask` from the fragment shader and
uses `gl::ZERO, gl::ONE_MINUS_SRC_COLOR` as the glBlendFuncs. This achieves:
@ -316,7 +348,7 @@ result_after_pass0.g = 0 * oFragColor.g + (1 - oFragColor.g) * dest.r
```
- The second pass outputs `text_color * mask` from the fragment shader and uses
`gl::ONE, gl::ONE` as the glBlendFuncs. This gets us:
`gl::ONE, gl::ONE` as the glBlendFuncs. This results in the correct overall blend equation.
```
oFragColor = text_color * mask;
@ -327,9 +359,15 @@ result_after_pass1.r
= text_color.r * mask.r + (1 - text_color.a * mask.r) * dest.r
```
And analogous results for the other channels.
#### 3. Dual source subpixel blending in OpenGL
This achieves what we set out to do, so we're done here.
The third approach is similar to the second approach, but makes use of the [`ARB_blend_func_extended`](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_blend_func_extended.txt) extension
in order to fold the two passes into one:
Instead of outputting the two different colors in two separate passes, we output them from the same pass,
as two separate fragment shader outputs.
Those outputs can then be treated as two different sources in the blend equation.
This method of text blending has not been implemented in WebRender yet.
## Subpixel Text Rendering to Transparent Destinations with a Background Color Hint

Просмотреть файл

@ -2,6 +2,15 @@
* 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/. */
//! This example creates a 200x200 white rect and allows the user to move it
//! around by using the arrow keys and rotate with '<'/'>'.
//! It does this by using the animation API.
//! The example also features seamless opaque/transparent split of a
//! rounded cornered rectangle, which is done automatically during the
//! scene building for render optimization.
extern crate euclid;
extern crate gleam;
extern crate glutin;
extern crate webrender;
@ -10,12 +19,11 @@ extern crate webrender;
mod boilerplate;
use boilerplate::{Example, HandyDandyRectBuilder};
use euclid::Radians;
use webrender::api::*;
// This example creates a 100x100 white rect and allows the user to move it
// around by using the arrow keys. It does this by using the animation API.
struct App {
property_key: PropertyBindingKey<LayoutTransform>,
transform: LayoutTransform,
}
@ -29,16 +37,22 @@ impl Example for App {
_pipeline_id: PipelineId,
_document_id: DocumentId,
) {
// Create a 100x100 stacking context with an animatable transform property.
// Note the magic "42" we use as the animation key. That is used to update
// the transform in the keyboard event handler code.
let bounds = (0, 0).to(100, 100);
let info = LayoutPrimitiveInfo::new(bounds);
// Create a 200x200 stacking context with an animated transform property.
let bounds = (0, 0).to(200, 200);
let complex_clip = ComplexClipRegion {
rect: bounds,
radii: BorderRadius::uniform(50.0),
mode: ClipMode::Clip,
};
let info = LayoutPrimitiveInfo {
local_clip: LocalClip::RoundedRect(bounds, complex_clip),
.. LayoutPrimitiveInfo::new(bounds)
};
builder.push_stacking_context(
&info,
ScrollPolicy::Scrollable,
Some(PropertyBinding::Binding(PropertyBindingKey::new(42))),
Some(PropertyBinding::Binding(self.property_key)),
TransformStyle::Flat,
None,
MixBlendMode::Normal,
@ -54,24 +68,27 @@ impl Example for App {
fn on_event(&mut self, event: glutin::Event, api: &RenderApi, document_id: DocumentId) -> bool {
match event {
glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
let offset = match key {
glutin::VirtualKeyCode::Down => (0.0, 10.0),
glutin::VirtualKeyCode::Up => (0.0, -10.0),
glutin::VirtualKeyCode::Right => (10.0, 0.0),
glutin::VirtualKeyCode::Left => (-10.0, 0.0),
let (offset_x, offset_y, angle) = match key {
glutin::VirtualKeyCode::Down => (0.0, 10.0, 0.0),
glutin::VirtualKeyCode::Up => (0.0, -10.0, 0.0),
glutin::VirtualKeyCode::Right => (10.0, 0.0, 0.0),
glutin::VirtualKeyCode::Left => (-10.0, 0.0, 0.0),
glutin::VirtualKeyCode::Comma => (0.0, 0.0, 0.1),
glutin::VirtualKeyCode::Period => (0.0, 0.0, -0.1),
_ => return false,
};
// Update the transform based on the keyboard input and push it to
// webrender using the generate_frame API. This will recomposite with
// the updated transform.
let new_transform = self.transform
.post_translate(LayoutVector3D::new(offset.0, offset.1, 0.0));
.pre_rotate(0.0, 0.0, 1.0, Radians::new(angle))
.post_translate(LayoutVector3D::new(offset_x, offset_y, 0.0));
api.generate_frame(
document_id,
Some(DynamicProperties {
transforms: vec![
PropertyValue {
key: PropertyBindingKey::new(42),
key: self.property_key,
value: new_transform,
},
],
@ -89,7 +106,8 @@ impl Example for App {
fn main() {
let mut app = App {
transform: LayoutTransform::identity(),
property_key: PropertyBindingKey::new(42), // arbitrary magic number
transform: LayoutTransform::create_translation(0.0, 0.0, 0.0),
};
boilerplate::main_wrapper(&mut app, None);
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше