Merge mozilla-central to b2g-inbound

This commit is contained in:
Carsten "Tomcat" Book 2014-11-13 16:38:44 +01:00
commit 3156cab275
190 changed files with 3343 additions and 1419 deletions

View File

@ -350,13 +350,14 @@ pref("browser.urlbar.match.title", "#");
pref("browser.urlbar.match.url", "@");
// The default behavior for the urlbar can be configured to use any combination
// of the restrict or match filters with each additional filter restricting
// more (intersection). Add the following values to set the behavior as the
// default: 1: history, 2: bookmark, 4: tag, 8: title, 16: url, 32: typed,
// 64: javascript, 128: tabs
// E.g., 0 = show all results (no filtering), 1 = only visited pages in history,
// 2 = only bookmarks, 3 = visited bookmarks, 1+16 = history matching in the url
pref("browser.urlbar.default.behavior", 0);
// of the match filters with each additional filter adding more results (union).
pref("browser.urlbar.suggest.history", true);
pref("browser.urlbar.suggest.bookmark", true);
pref("browser.urlbar.suggest.openpage", true);
// Restrictions to current suggestions can also be applied (intersection).
// Typed suggestion works only if history is set to true.
pref("browser.urlbar.suggest.history.onlyTyped", false);
pref("browser.urlbar.formatting.enabled", true);
pref("browser.urlbar.trimURLs", true);
@ -1262,7 +1263,6 @@ pref("services.sync.prefs.sync.browser.tabs.loadInBackground", true);
pref("services.sync.prefs.sync.browser.tabs.warnOnClose", true);
pref("services.sync.prefs.sync.browser.tabs.warnOnOpen", true);
pref("services.sync.prefs.sync.browser.urlbar.autocomplete.enabled", true);
pref("services.sync.prefs.sync.browser.urlbar.default.behavior", true);
pref("services.sync.prefs.sync.browser.urlbar.maxRichResults", true);
pref("services.sync.prefs.sync.dom.disable_open_during_load", true);
pref("services.sync.prefs.sync.dom.disable_window_flip", true);

View File

@ -43,10 +43,12 @@ let gCustomize = {
return Promise.resolve(nodes);
}
panel.hidden = false;
panel.openPopup(button);
button.setAttribute("active", true);
panel.addEventListener("popuphidden", function onHidden() {
panel.removeEventListener("popuphidden", onHidden);
panel.hidden = true;
button.removeAttribute("active");
});

View File

@ -20,6 +20,7 @@ let gIntro = {
}
this._nodes.panel.addEventListener("popupshowing", e => this._setUpPanel());
this._nodes.panel.addEventListener("popuphidden", e => this._hidePanel());
this._nodes.what.addEventListener("click", e => this.showPanel());
},
@ -32,6 +33,7 @@ let gIntro = {
showPanel: function() {
// Point the panel at the 'what' link
this._nodes.panel.hidden = false;
this._nodes.panel.openPopup(this._nodes.what);
},
@ -48,4 +50,8 @@ let gIntro = {
});
}
},
_hidePanel: function() {
this._nodes.panel.hidden = true;
}
};

View File

@ -21,19 +21,19 @@
title="&newtab.pageTitle;">
<xul:panel id="newtab-intro-panel" orient="vertical" type="arrow"
noautohide="true">
noautohide="true" hidden="true">
<h1>&newtab.intro.header;</h1>
</xul:panel>
<xul:panel id="newtab-search-panel" orient="vertical" type="arrow"
noautohide="true">
noautohide="true" hidden="true">
<xul:hbox id="newtab-search-manage" class="newtab-search-panel-engine">
<xul:label>&cmd_engineManager.label;</xul:label>
</xul:hbox>
</xul:panel>
<xul:panel id="newtab-customize-panel" orient="vertical" type="arrow"
noautohide="true">
noautohide="true" hidden="true">
<xul:hbox id="newtab-customize-enhanced" class="newtab-customize-panel-item">
<xul:label>&newtab.customize.enhanced;</xul:label>
</xul:hbox>

View File

@ -4,6 +4,9 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#endif
// The amount of time we wait while coalescing updates for hidden pages.
const SCHEDULE_UPDATE_TIMEOUT_MS = 1000;
/**
* This singleton represents the whole 'New Tab Page' and takes care of
* initializing all its components.
@ -69,16 +72,39 @@ let gPage = {
},
/**
* Updates the whole page and the grid when the storage has changed.
* @param aOnlyIfHidden If true, the page is updated only if it's hidden in
* the preloader.
* Updates the page's grid right away for visible pages. If the page is
* currently hidden, i.e. in a background tab or in the preloader, then we
* batch multiple update requests and refresh the grid once after a short
* delay. Accepts a single parameter the specifies the reason for requesting
* a page update. The page may decide to delay or prevent a requested updated
* based on the given reason.
*/
update: function Page_update(aOnlyIfHidden=false) {
let skipUpdate = aOnlyIfHidden && !document.hidden;
// The grid might not be ready yet as we initialize it asynchronously.
if (gGrid.ready && !skipUpdate) {
gGrid.refresh();
update(reason = "") {
// Update immediately if we're visible.
if (!document.hidden) {
// Ignore updates where reason=links-changed as those signal that the
// provider's set of links changed. We don't want to update visible pages
// in that case, it is ok to wait until the user opens the next tab.
if (reason != "links-changed" && gGrid.ready) {
gGrid.refresh();
}
return;
}
// Bail out if we scheduled before.
if (this._scheduleUpdateTimeout) {
return;
}
this._scheduleUpdateTimeout = setTimeout(() => {
// Refresh if the grid is ready.
if (gGrid.ready) {
gGrid.refresh();
}
this._scheduleUpdateTimeout = null;
}, SCHEDULE_UPDATE_TIMEOUT_MS);
},
/**
@ -170,6 +196,15 @@ let gPage = {
}
break;
case "visibilitychange":
// Cancel any delayed updates for hidden pages now that we're visible.
if (this._scheduleUpdateTimeout) {
clearTimeout(this._scheduleUpdateTimeout);
this._scheduleUpdateTimeout = null;
// An update was pending so force an update now.
this.update();
}
setTimeout(() => this.onPageFirstVisible());
removeEventListener("visibilitychange", this);
break;

View File

@ -21,10 +21,12 @@ let gSearch = {
showPanel: function () {
let panel = this._nodes.panel;
let logo = this._nodes.logo;
panel.hidden = false;
panel.openPopup(logo);
logo.setAttribute("active", "true");
panel.addEventListener("popuphidden", function onHidden() {
panel.removeEventListener("popuphidden", onHidden);
panel.hidden = true;
logo.removeAttribute("active");
});
},

View File

@ -44,6 +44,10 @@ function runTests() {
"New page grid is updated correctly.");
gBrowser.removeTab(newTab);
// Wait until the original tab is visible again.
let doc = existingTab.linkedBrowser.contentDocument;
yield waitForCondition(() => !doc.hidden).then(TestRunner.next);
}
gBrowser.removeTab(existingTab);

View File

@ -354,8 +354,8 @@ let checkCurrentEngine = Task.async(function* ({name: basename, logoPrefix1x, lo
ok(base64.startsWith(expectedLogoPrefix), "Checking image prefix.");
logo.click();
let panel = searchPanel();
panel.openPopup(logo);
yield promisePanelShown(panel);
panel.hidePopup();

View File

@ -1,44 +1,32 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Checks that newtab is updated as its links change.
*/
function runTests() {
// First, start with an empty page. setLinks will trigger a hidden page
// update because it calls clearHistory. We need to wait for that update to
// happen so that the next time we wait for a page update below, we catch the
// right update and not the one triggered by setLinks.
//
// Why this weird way of yielding? First, these two functions don't return
// promises, they call TestRunner.next when done. Second, the point at which
// setLinks is done is independent of when the page update will happen, so
// calling whenPagesUpdated cannot wait until that time.
setLinks([]);
whenPagesUpdated(null, true);
yield null;
yield null;
yield whenPagesUpdatedAnd(resolve => setLinks([], resolve));
// Strategy: Add some visits, open a new page, check the grid, repeat.
fillHistory([link(1)]);
yield whenPagesUpdated(null, true);
yield fillHistoryAndWaitForPageUpdate([1]);
yield addNewTabPageTab();
checkGrid("1,,,,,,,,");
fillHistory([link(2)]);
yield whenPagesUpdated(null, true);
yield fillHistoryAndWaitForPageUpdate([2]);
yield addNewTabPageTab();
checkGrid("2,1,,,,,,,");
fillHistory([link(1)]);
yield whenPagesUpdated(null, true);
yield fillHistoryAndWaitForPageUpdate([1]);
yield addNewTabPageTab();
checkGrid("1,2,,,,,,,");
// Wait for fillHistory to add all links before waiting for an update
yield fillHistory([link(2), link(3), link(4)], TestRunner.next);
yield whenPagesUpdated(null, true);
yield fillHistoryAndWaitForPageUpdate([2, 3, 4]);
yield addNewTabPageTab();
checkGrid("2,1,3,4,,,,,");
@ -46,6 +34,16 @@ function runTests() {
is(getCell(1).site.link.type, "history", "added link is history");
}
function fillHistoryAndWaitForPageUpdate(links) {
return whenPagesUpdatedAnd(resolve => fillHistory(links.map(link), resolve));
}
function whenPagesUpdatedAnd(promiseConstructor) {
let promise1 = new Promise(whenPagesUpdated);
let promise2 = new Promise(promiseConstructor);
return Promise.all([promise1, promise2]).then(TestRunner.next);
}
function link(id) {
return { url: "http://example" + id + ".com/", title: "site#" + id };
}

View File

@ -214,7 +214,7 @@ function getCell(aIndex) {
* {url: "http://example2.com/", title: "site#2"},
* {url: "http://example3.com/", title: "site#3"}]
*/
function setLinks(aLinks) {
function setLinks(aLinks, aCallback = TestRunner.next) {
let links = aLinks;
if (typeof links == "string") {
@ -233,7 +233,7 @@ function setLinks(aLinks) {
fillHistory(links, function () {
NewTabUtils.links.populateCache(function () {
NewTabUtils.allPages.update();
TestRunner.next();
aCallback();
}, true);
});
});
@ -249,7 +249,7 @@ function clearHistory(aCallback) {
PlacesUtils.history.removeAllPages();
}
function fillHistory(aLinks, aCallback) {
function fillHistory(aLinks, aCallback = TestRunner.next) {
let numLinks = aLinks.length;
if (!numLinks) {
if (aCallback)
@ -323,6 +323,33 @@ function restore() {
NewTabUtils.restore();
}
/**
* Wait until a given condition becomes true.
*/
function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) {
return new Promise((resolve, reject) => {
let tries = 0;
function tryNow() {
tries++;
if (aConditionFn()) {
resolve();
} else if (tries < aMaxTries) {
tryAgain();
} else {
reject("Condition timed out: " + aConditionFn.toSource());
}
}
function tryAgain() {
setTimeout(tryNow, aCheckInterval);
}
tryAgain();
});
}
/**
* Creates a new tab containing 'about:newtab'.
*/
@ -349,7 +376,7 @@ function addNewTabPageTabPromise() {
// The new tab page might have been preloaded in the background.
if (browser.contentDocument.readyState == "complete") {
whenNewTabLoaded();
waitForCondition(() => !browser.contentDocument.hidden).then(whenNewTabLoaded);
return deferred.promise;
}
@ -617,18 +644,14 @@ function createDragEvent(aEventType, aData) {
/**
* Resumes testing when all pages have been updated.
* @param aCallback Called when done. If not specified, TestRunner.next is used.
* @param aOnlyIfHidden If true, this resumes testing only when an update that
* applies to pre-loaded, hidden pages is observed. If
* false, this resumes testing when any update is observed.
*/
function whenPagesUpdated(aCallback, aOnlyIfHidden=false) {
function whenPagesUpdated(aCallback = TestRunner.next) {
let page = {
observe: _ => _,
update: function (onlyIfHidden=false) {
if (onlyIfHidden == aOnlyIfHidden) {
NewTabUtils.allPages.unregister(this);
executeSoon(aCallback || TestRunner.next);
}
update() {
NewTabUtils.allPages.unregister(this);
executeSoon(aCallback);
}
};

View File

@ -743,16 +743,19 @@ html, .fx-embedded, #main,
position: relative;
}
.standalone .room-inner-action-area {
.standalone .room-inner-info-area {
position: absolute;
top: 35%;
left: 0;
right: 25%;
z-index: 1000;
margin: 0 auto;
width: 50%;
color: #fff;
font-weight: bold;
}
.standalone .room-inner-action-area button {
position: absolute;
.standalone .room-inner-info-area button {
border-radius: 3px;
font-size: 1.2em;
padding: .2em 1.2em;

View File

@ -129,19 +129,33 @@ loop.standaloneRoomViews = (function(mozL10n) {
this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS;
},
_renderActionButtons: function() {
_renderContextualRoomInfo: function() {
switch(this.state.roomState) {
case ROOM_STATES.INIT:
case ROOM_STATES.READY: {
// Join button
return (
React.DOM.div({className: "room-inner-info-area"},
React.DOM.button({className: "btn btn-join btn-info", onClick: this.joinRoom},
mozL10n.get("rooms_room_join_label")
)
)
);
}
case ROOM_STATES.JOINED:
case ROOM_STATES.SESSION_CONNECTED: {
// Empty room message
return (
React.DOM.div({className: "room-inner-info-area"},
React.DOM.p({className: "empty-room-message"},
mozL10n.get("rooms_only_occupant_label")
)
)
);
}
}
// XXX Render "Start your own" button when room is over capacity (see
// bug 1074709)
if (this.state.roomState === ROOM_STATES.INIT ||
this.state.roomState === ROOM_STATES.READY) {
return (
React.DOM.div({className: "room-inner-action-area"},
React.DOM.button({className: "btn btn-join btn-info", onClick: this.joinRoom},
mozL10n.get("rooms_room_join_label")
)
)
);
}
},
render: function() {
@ -154,7 +168,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
return (
React.DOM.div({className: "room-conversation-wrapper"},
this._renderActionButtons(),
this._renderContextualRoomInfo(),
React.DOM.div({className: "video-layout-wrapper"},
React.DOM.div({className: "conversation room-conversation"},
React.DOM.h2({className: "room-name"}, this.state.roomName),

View File

@ -129,19 +129,33 @@ loop.standaloneRoomViews = (function(mozL10n) {
this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS;
},
_renderActionButtons: function() {
_renderContextualRoomInfo: function() {
switch(this.state.roomState) {
case ROOM_STATES.INIT:
case ROOM_STATES.READY: {
// Join button
return (
<div className="room-inner-info-area">
<button className="btn btn-join btn-info" onClick={this.joinRoom}>
{mozL10n.get("rooms_room_join_label")}
</button>
</div>
);
}
case ROOM_STATES.JOINED:
case ROOM_STATES.SESSION_CONNECTED: {
// Empty room message
return (
<div className="room-inner-info-area">
<p className="empty-room-message">
{mozL10n.get("rooms_only_occupant_label")}
</p>
</div>
);
}
}
// XXX Render "Start your own" button when room is over capacity (see
// bug 1074709)
if (this.state.roomState === ROOM_STATES.INIT ||
this.state.roomState === ROOM_STATES.READY) {
return (
<div className="room-inner-action-area">
<button className="btn btn-join btn-info" onClick={this.joinRoom}>
{mozL10n.get("rooms_room_join_label")}
</button>
</div>
);
}
},
render: function() {
@ -154,7 +168,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
return (
<div className="room-conversation-wrapper">
{this._renderActionButtons()}
{this._renderContextualRoomInfo()}
<div className="video-layout-wrapper">
<div className="conversation room-conversation">
<h2 className="room-name">{this.state.roomName}</h2>

View File

@ -102,6 +102,32 @@ describe("loop.standaloneRoomViews", function() {
view = mountTestComponent();
});
describe("Empty room message", function() {
it("should display an empty room message on JOINED",
function() {
activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINED});
expect(view.getDOMNode().querySelector(".empty-room-message"))
.not.eql(null);
});
it("should display an empty room message on SESSION_CONNECTED",
function() {
activeRoomStore.setStoreState({roomState: ROOM_STATES.SESSION_CONNECTED});
expect(view.getDOMNode().querySelector(".empty-room-message"))
.not.eql(null);
});
it("shouldn't display an empty room message on HAS_PARTICIPANTS",
function() {
activeRoomStore.setStoreState({roomState: ROOM_STATES.HAS_PARTICIPANTS});
expect(view.getDOMNode().querySelector(".empty-room-message"))
.eql(null);
});
});
describe("Join button", function() {
function getJoinButton(view) {
return view.getDOMNode().querySelector(".btn-join");

View File

@ -569,6 +569,15 @@
)
),
Example({summary: "Standalone room conversation (joined)"},
React.DOM.div({className: "standalone"},
StandaloneRoomView({
dispatcher: dispatcher,
activeRoomStore: activeRoomStore,
roomState: ROOM_STATES.JOINED})
)
),
Example({summary: "Standalone room conversation (has-participants)"},
React.DOM.div({className: "standalone"},
StandaloneRoomView({

View File

@ -569,6 +569,15 @@
</div>
</Example>
<Example summary="Standalone room conversation (joined)">
<div className="standalone">
<StandaloneRoomView
dispatcher={dispatcher}
activeRoomStore={activeRoomStore}
roomState={ROOM_STATES.JOINED} />
</div>
</Example>
<Example summary="Standalone room conversation (has-participants)">
<div className="standalone">
<StandaloneRoomView

View File

@ -230,7 +230,7 @@ BrowserGlue.prototype = {
},
#endif
// nsIObserver implementation
// nsIObserver implementation
observe: function BG_observe(subject, topic, data) {
switch (topic) {
case "prefservice:after-app-defaults":
@ -1459,7 +1459,7 @@ BrowserGlue.prototype = {
},
_migrateUI: function BG__migrateUI() {
const UI_VERSION = 25;
const UI_VERSION = 26;
const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
let currentUIVersion = 0;
try {
@ -1735,6 +1735,40 @@ BrowserGlue.prototype = {
catch (ex) {}
}
if (currentUIVersion < 26) {
// Refactor urlbar suggestion preferences to make it extendable and
// allow new suggestion types (e.g: search suggestions).
let types = ["history", "bookmark", "openpage"];
let defaultBehavior = 0;
try {
defaultBehavior = Services.prefs.getIntPref("browser.urlbar.default.behavior");
} catch (ex) {}
try {
let autocompleteEnabled = Services.prefs.getBoolPref("browser.urlbar.autocomplete.enabled");
if (!autocompleteEnabled) {
defaultBehavior = -1;
}
} catch (ex) {}
// If the default behavior is:
// -1 - all new "...suggest.*" preferences will be false
// 0 - all new "...suggest.*" preferences will use the default values
// > 0 - all new "...suggest.*" preferences will be inherited
for (let type of types) {
let prefValue = defaultBehavior == 0;
if (defaultBehavior > 0) {
prefValue = !!(defaultBehavior & Ci.mozIPlacesAutoComplete["BEHAVIOR_" + type.toUpperCase()]);
}
Services.prefs.setBoolPref("browser.urlbar.suggest." + type, prefValue);
}
// Typed behavior will be used only for results from history.
if (defaultBehavior != -1 &&
!!(defaultBehavior & Ci.mozIPlacesAutoComplete["BEHAVIOR_TYPED"])) {
Services.prefs.setBoolPref("browser.urlbar.suggest.history.onlyTyped", true);
}
}
// Update the migration version.
Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
},

View File

@ -0,0 +1,150 @@
/* 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/. */
const UI_VERSION = 26;
const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
const TOPICDATA_BROWSERGLUE_TEST = "force-ui-migration";
const DEFAULT_BEHAVIOR_PREF = "browser.urlbar.default.behavior";
const AUTOCOMPLETE_PREF = "browser.urlbar.autocomplete.enabled";
let gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"]
.getService(Ci.nsIObserver);
let gGetBoolPref = Services.prefs.getBoolPref;
function run_test() {
run_next_test();
};
do_register_cleanup(cleanup);
function cleanup() {
let prefix = "browser.urlbar.suggest.";
for (let type of ["history", "bookmark", "openpage", "history.onlyTyped"]) {
Services.prefs.clearUserPref(prefix + type);
}
Services.prefs.clearUserPref("browser.migration.version");
Services.prefs.clearUserPref(AUTOCOMPLETE_PREF);
};
function setupBehaviorAndMigrate(aDefaultBehavior, aAutocompleteEnabled = true) {
cleanup();
// Migrate browser.urlbar.default.behavior preference.
Services.prefs.setIntPref("browser.migration.version", UI_VERSION - 1);
Services.prefs.setIntPref(DEFAULT_BEHAVIOR_PREF, aDefaultBehavior);
Services.prefs.setBoolPref(AUTOCOMPLETE_PREF, aAutocompleteEnabled);
// Simulate a migration.
gBrowserGlue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_BROWSERGLUE_TEST);
};
add_task(function*() {
do_log_info("Migrate default.behavior = 0");
setupBehaviorAndMigrate(0);
Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"),
"History preference should be true.");
Assert.ok(gGetBoolPref("browser.urlbar.suggest.bookmark"),
"Bookmark preference should be true.");
Assert.ok(gGetBoolPref("browser.urlbar.suggest.openpage"),
"Openpage preference should be true.");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false,
"Typed preference should be false.");
});
add_task(function*() {
do_log_info("Migrate default.behavior = 1");
setupBehaviorAndMigrate(1);
Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"),
"History preference should be true.");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.bookmark"), false,
"Bookmark preference should be false.");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false,
"Openpage preference should be false");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false,
"Typed preference should be false");
});
add_task(function*() {
do_log_info("Migrate default.behavior = 2");
setupBehaviorAndMigrate(2);
Assert.equal(gGetBoolPref("browser.urlbar.suggest.history"), false,
"History preference should be false.");
Assert.ok(gGetBoolPref("browser.urlbar.suggest.bookmark"),
"Bookmark preference should be true.");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false,
"Openpage preference should be false");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false,
"Typed preference should be false");
});
add_task(function*() {
do_log_info("Migrate default.behavior = 3");
setupBehaviorAndMigrate(3);
Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"),
"History preference should be true.");
Assert.ok(gGetBoolPref("browser.urlbar.suggest.bookmark"),
"Bookmark preference should be true.");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false,
"Openpage preference should be false");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false,
"Typed preference should be false");
});
add_task(function*() {
do_log_info("Migrate default.behavior = 19");
setupBehaviorAndMigrate(19);
Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"),
"History preference should be true.");
Assert.ok(gGetBoolPref("browser.urlbar.suggest.bookmark"),
"Bookmark preference should be true.");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false,
"Openpage preference should be false");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false,
"Typed preference should be false");
});
add_task(function*() {
do_log_info("Migrate default.behavior = 33");
setupBehaviorAndMigrate(33);
Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"),
"History preference should be true.");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.bookmark"), false,
"Bookmark preference should be false.");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false,
"Openpage preference should be false");
Assert.ok(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"),
"Typed preference should be true");
});
add_task(function*() {
do_log_info("Migrate default.behavior = 129");
setupBehaviorAndMigrate(129);
Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"),
"History preference should be true.");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.bookmark"), false,
"Bookmark preference should be false.");
Assert.ok(gGetBoolPref("browser.urlbar.suggest.openpage"),
"Openpage preference should be true");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false,
"Typed preference should be false");
});
add_task(function*() {
do_log_info("Migrate default.behavior = 0, autocomplete.enabled = false");
setupBehaviorAndMigrate(0, false);
Assert.equal(gGetBoolPref("browser.urlbar.suggest.history"), false,
"History preference should be false.");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.bookmark"), false,
"Bookmark preference should be false.");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.openpage"), false,
"Openpage preference should be false");
Assert.equal(gGetBoolPref("browser.urlbar.suggest.history.onlyTyped"), false,
"Typed preference should be false");
});

View File

@ -19,6 +19,7 @@ support-files =
[test_browserGlue_prefs.js]
[test_browserGlue_restore.js]
[test_browserGlue_smartBookmarks.js]
[test_browserGlue_urlbar_defaultbehavior_migration.js]
[test_clearHistory_shutdown.js]
[test_leftpane_corruption_handling.js]
[test_PUIU_makeTransaction.js]

View File

@ -32,6 +32,25 @@ var gPrivacyPane = {
},
#endif
/**
* Initialize autocomplete to ensure prefs are in sync.
*/
_initAutocomplete: function () {
let unifiedCompletePref = false;
try {
unifiedCompletePref =
Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete");
} catch (ex) {}
if (unifiedCompletePref) {
Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
.getService(Components.interfaces.mozIPlacesAutoComplete);
} else {
Components.classes["@mozilla.org/autocomplete/search;1?name=history"]
.getService(Components.interfaces.mozIPlacesAutoComplete);
}
},
/**
* Sets up the UI for the number of days of history to keep, and updates the
* label of the "Clear Now..." button.
@ -52,11 +71,8 @@ var gPrivacyPane = {
#ifdef NIGHTLY_BUILD
this._initTrackingProtection();
#endif
this._initAutocomplete();
setEventListener("browser.urlbar.default.behavior", "change",
document.getElementById('browser.urlbar.autocomplete.enabled')
.updateElements
);
setEventListener("privacy.sanitize.sanitizeOnShutdown", "change",
gPrivacyPane._updateSanitizeSettingsButton);
setEventListener("browser.privatebrowsing.autostart", "change",
@ -203,13 +219,8 @@ var gPrivacyPane = {
// select the remember forms history option
document.getElementById("browser.formfill.enable").value = true;
#ifdef RELEASE_BUILD
// select the allow cookies option
document.getElementById("network.cookie.cookieBehavior").value = 0;
#else
// select the limit cookies option
document.getElementById("network.cookie.cookieBehavior").value = 3;
#endif
// select the cookie lifetime policy option
document.getElementById("network.cookie.lifetimePolicy").value = 0;
@ -333,40 +344,16 @@ var gPrivacyPane = {
// HISTORY
/**
* Read the location bar enabled and suggestion prefs
* @return Int value for suggestion menulist
* Update browser.urlbar.autocomplete.enabled when a
* browser.urlbar.suggest.* pref is changed from the ui.
*/
readSuggestionPref: function PPP_readSuggestionPref()
{
let getVal = function(aPref)
document.getElementById("browser.urlbar." + aPref).value;
// Suggest nothing if autocomplete is not enabled
if (!getVal("autocomplete.enabled"))
return -1;
// Bottom 2 bits of default.behavior specify history/bookmark
return getVal("default.behavior") & 3;
},
/**
* Write the location bar enabled and suggestion prefs when necessary
* @return Bool value for enabled pref
*/
writeSuggestionPref: function PPP_writeSuggestionPref()
{
let menuVal = document.getElementById("locationBarSuggestion").value;
let enabled = menuVal != -1;
// Only update default.behavior if we're giving suggestions
if (enabled) {
// Put the selected menu item's value directly into the bottom 2 bits
let behavior = document.getElementById("browser.urlbar.default.behavior");
behavior.value = behavior.value >> 2 << 2 | menuVal;
writeSuggestionPref: function () {
let getVal = (aPref) => {
return document.getElementById("browser.urlbar.suggest." + aPref).value;
}
// Always update the enabled pref
return enabled;
// autocomplete.enabled is true if any of the suggestions is true
let enabled = ["history", "bookmark", "openpage"].map(getVal).some(v => v);
Services.prefs.setBoolPref("browser.urlbar.autocomplete.enabled", enabled);
},
/*
@ -429,19 +416,11 @@ var gPrivacyPane = {
var accept = document.getElementById("acceptCookies");
var acceptThirdPartyMenu = document.getElementById("acceptThirdPartyMenu");
#ifdef RELEASE_BUILD
// if we're enabling cookies, automatically select 'accept third party always'
if (accept.checked)
acceptThirdPartyMenu.selectedIndex = 0;
return accept.checked ? 0 : 2;
#else
// if we're enabling cookies, automatically select 'accept third party from visited'
if (accept.checked)
acceptThirdPartyMenu.selectedIndex = 1;
return accept.checked ? 3 : 2;
#endif
},
/**

View File

@ -29,9 +29,15 @@
<preference id="browser.urlbar.autocomplete.enabled"
name="browser.urlbar.autocomplete.enabled"
type="bool"/>
<preference id="browser.urlbar.default.behavior"
name="browser.urlbar.default.behavior"
type="int"/>
<preference id="browser.urlbar.suggest.bookmark"
name="browser.urlbar.suggest.bookmark"
type="bool"/>
<preference id="browser.urlbar.suggest.history"
name="browser.urlbar.suggest.history"
type="bool"/>
<preference id="browser.urlbar.suggest.openpage"
name="browser.urlbar.suggest.openpage"
type="bool"/>
<!-- History -->
<preference id="places.history.enabled"
@ -231,23 +237,21 @@
</groupbox>
<!-- Location Bar -->
<groupbox id="locationBarGroup" data-category="panePrivacy" hidden="true">
<groupbox id="locationBarGroup"
data-category="panePrivacy"
hidden="true">
<caption><label>&locationBar.label;</label></caption>
<hbox align="center">
<label id="locationBarSuggestionLabel"
control="locationBarSuggestion"
accesskey="&locbar.pre.accessKey;">&locbar.pre.label;</label>
<menulist id="locationBarSuggestion"
preference="browser.urlbar.autocomplete.enabled"
onsyncfrompreference="return gPrivacyPane.readSuggestionPref();"
onsynctopreference="return gPrivacyPane.writeSuggestionPref();">
<menupopup>
<menuitem label="&locbar.both.label;" value="0"/>
<menuitem label="&locbar.history.label;" value="1"/>
<menuitem label="&locbar.bookmarks.label;" value="2"/>
<menuitem label="&locbar.nothing.label;" value="-1"/>
</menupopup>
</menulist>
<label>&locbar.post.label;</label>
</hbox>
<label id="locationBarSuggestionLabel">&locbar.pre.label;</label>
<checkbox id="historySuggestion" label="&locbar.history.label;"
onsyncfrompreference="return gPrivacyPane.writeSuggestionPref();"
accesskey="&locbar.history.accesskey;"
preference="browser.urlbar.suggest.history"/>
<checkbox id="bookmarkSuggestion" label="&locbar.bookmarks.label;"
onsyncfrompreference="return gPrivacyPane.writeSuggestionPref();"
accesskey="&locbar.bookmarks.accesskey;"
preference="browser.urlbar.suggest.bookmark"/>
<checkbox id="openpageSuggestion" label="&locbar.openpage.label;"
onsyncfrompreference="return gPrivacyPane.writeSuggestionPref();"
accesskey="&locbar.openpage.accesskey;"
preference="browser.urlbar.suggest.openpage"/>
</groupbox>

View File

@ -17,14 +17,10 @@ function test() {
test_custom_retention("acceptCookies", "remember"),
test_custom_retention("acceptCookies", "custom")
],
(runtime.isReleaseBuild ? [
[
test_custom_retention("acceptThirdPartyMenu", "remember", "visited"),
test_custom_retention("acceptThirdPartyMenu", "custom", "always")
]
: [
test_custom_retention("acceptThirdPartyMenu", "remember", "always"),
test_custom_retention("acceptThirdPartyMenu", "custom", "visited")
]), [
], [
test_custom_retention("keepCookiesUntil", "remember", 1),
test_custom_retention("keepCookiesUntil", "custom", 2),
test_custom_retention("keepCookiesUntil", "custom", 0),

View File

@ -13,13 +13,13 @@ function test() {
loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
run_test_subset([
test_locbar_suggestion_retention(-1, undefined),
test_locbar_suggestion_retention(1, -1),
test_locbar_suggestion_retention(2, 1),
test_locbar_suggestion_retention(0, 2),
test_locbar_suggestion_retention(0, 0),
test_locbar_suggestion_retention("history", true),
test_locbar_suggestion_retention("bookmark", true),
test_locbar_suggestion_retention("openpage", false),
test_locbar_suggestion_retention("history", true),
test_locbar_suggestion_retention("history", false),
// reset all preferences to their default values once we're done
reset_preferences
]);
}
}

View File

@ -301,18 +301,14 @@ function test_custom_retention(controlToChange, expect, valueIncrement) {
};
}
function test_locbar_suggestion_retention(mode, expect) {
function test_locbar_suggestion_retention(suggestion, autocomplete) {
return function(win) {
let locbarsuggest = win.document.getElementById("locationBarSuggestion");
ok(locbarsuggest, "location bar suggestion menulist should exist");
let elem = win.document.getElementById(suggestion + "Suggestion");
ok(elem, "Suggest " + suggestion + " checkbox should exist.");
elem.click();
if (expect !== undefined) {
is(locbarsuggest.value, expect,
"location bar suggestion is expected to remain " + expect);
}
locbarsuggest.value = mode;
controlChanged(locbarsuggest);
is(Services.prefs.getBoolPref("browser.urlbar.autocomplete.enabled"), autocomplete,
"browser.urlbar.autocomplete.enabled pref should be " + autocomplete);
};
}

View File

@ -35,6 +35,25 @@ var gPrivacyPane = {
},
#endif
/**
* Initialize autocomplete to ensure prefs are in sync.
*/
_initAutocomplete: function () {
let unifiedCompletePref = false;
try {
unifiedCompletePref =
Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete");
} catch (ex) {}
if (unifiedCompletePref) {
Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
.getService(Components.interfaces.mozIPlacesAutoComplete);
} else {
Components.classes["@mozilla.org/autocomplete/search;1?name=history"]
.getService(Components.interfaces.mozIPlacesAutoComplete);
}
},
/**
* Sets up the UI for the number of days of history to keep, and updates the
* label of the "Clear Now..." button.
@ -49,6 +68,7 @@ var gPrivacyPane = {
#ifdef NIGHTLY_BUILD
this._initTrackingProtection();
#endif
this._initAutocomplete();
},
// HISTORY MODE
@ -165,13 +185,8 @@ var gPrivacyPane = {
// select the remember forms history option
document.getElementById("browser.formfill.enable").value = true;
#ifdef RELEASE_BUILD
// select the accept cookies option
document.getElementById("network.cookie.cookieBehavior").value = 0;
#else
// select the limit cookies option
document.getElementById("network.cookie.cookieBehavior").value = 3;
#endif
// select the cookie lifetime policy option
document.getElementById("network.cookie.lifetimePolicy").value = 0;
@ -296,40 +311,16 @@ var gPrivacyPane = {
// HISTORY
/**
* Read the location bar enabled and suggestion prefs
* @return Int value for suggestion menulist
* Update browser.urlbar.autocomplete.enabled when a
* browser.urlbar.suggest.* pref is changed from the ui.
*/
readSuggestionPref: function PPP_readSuggestionPref()
{
let getVal = function(aPref)
document.getElementById("browser.urlbar." + aPref).value;
// Suggest nothing if autocomplete is not enabled
if (!getVal("autocomplete.enabled"))
return -1;
// Bottom 2 bits of default.behavior specify history/bookmark
return getVal("default.behavior") & 3;
},
/**
* Write the location bar enabled and suggestion prefs when necessary
* @return Bool value for enabled pref
*/
writeSuggestionPref: function PPP_writeSuggestionPref()
{
let menuVal = document.getElementById("locationBarSuggestion").value;
let enabled = menuVal != -1;
// Only update default.behavior if we're giving suggestions
if (enabled) {
// Put the selected menu item's value directly into the bottom 2 bits
let behavior = document.getElementById("browser.urlbar.default.behavior");
behavior.value = behavior.value >> 2 << 2 | menuVal;
writeSuggestionPref: function PPP_writeSuggestionPref() {
let getVal = (aPref) => {
return document.getElementById("browser.urlbar.suggest." + aPref).value;
}
// Always update the enabled pref
return enabled;
// autocomplete.enabled is true if any of the suggestions is true
let enabled = ["history", "bookmark", "openpage"].map(getVal).some(v => v);
Services.prefs.setBoolPref("browser.urlbar.autocomplete.enabled", enabled);
},
/*
@ -392,19 +383,11 @@ var gPrivacyPane = {
var accept = document.getElementById("acceptCookies");
var acceptThirdPartyMenu = document.getElementById("acceptThirdPartyMenu");
#ifdef RELEASE_BUILD
// if we're enabling cookies, automatically select 'accept third party always'
if (accept.checked)
acceptThirdPartyMenu.selectedIndex = 0;
return accept.checked ? 0 : 2;
#else
// if we're enabling cookies, automatically select 'accept third party from visited'
if (accept.checked)
acceptThirdPartyMenu.selectedIndex = 1;
return accept.checked ? 3 : 2;
#endif
},
/**

View File

@ -42,10 +42,15 @@
<preference id="browser.urlbar.autocomplete.enabled"
name="browser.urlbar.autocomplete.enabled"
type="bool"/>
<preference id="browser.urlbar.default.behavior"
name="browser.urlbar.default.behavior"
type="int"
onchange="document.getElementById('browser.urlbar.autocomplete.enabled').updateElements();"/>
<preference id="browser.urlbar.suggest.bookmark"
name="browser.urlbar.suggest.bookmark"
type="bool"/>
<preference id="browser.urlbar.suggest.history"
name="browser.urlbar.suggest.history"
type="bool"/>
<preference id="browser.urlbar.suggest.openpage"
name="browser.urlbar.suggest.openpage"
type="bool"/>
<!-- History -->
<preference id="places.history.enabled"
@ -254,23 +259,22 @@
<groupbox id="locationBarGroup">
<caption label="&locationBar.label;"/>
<hbox align="center">
<label id="locationBarSuggestionLabel"
control="locationBarSuggestion"
accesskey="&locbar.pre.accessKey;">&locbar.pre.label;</label>
<menulist id="locationBarSuggestion"
preference="browser.urlbar.autocomplete.enabled"
onsyncfrompreference="return gPrivacyPane.readSuggestionPref();"
onsynctopreference="return gPrivacyPane.writeSuggestionPref();">
<menupopup>
<menuitem label="&locbar.both.label;" value="0"/>
<menuitem label="&locbar.history.label;" value="1"/>
<menuitem label="&locbar.bookmarks.label;" value="2"/>
<menuitem label="&locbar.nothing.label;" value="-1"/>
</menupopup>
</menulist>
<label>&locbar.post.label;</label>
</hbox>
<label id="locationBarSuggestionLabel">&locbar.pre.label;</label>
<vbox id="tabPrefsBox" align="start" flex="1">
<checkbox id="historySuggestion" label="&locbar.history.label;"
onsyncfrompreference="return gPrivacyPane.writeSuggestionPref();"
accesskey="&locbar.history.accesskey;"
preference="browser.urlbar.suggest.history"/>
<checkbox id="bookmarkSuggestion" label="&locbar.bookmarks.label;"
onsyncfrompreference="return gPrivacyPane.writeSuggestionPref();"
accesskey="&locbar.bookmarks.accesskey;"
preference="browser.urlbar.suggest.bookmark"/>
<checkbox id="openpageSuggestion" label="&locbar.openpage.label;"
onsyncfrompreference="return gPrivacyPane.writeSuggestionPref();"
accesskey="&locbar.openpage.accesskey;"
preference="browser.urlbar.suggest.openpage"/>
</vbox>
</groupbox>
</prefpane>

View File

@ -19,5 +19,4 @@ skip-if = !healthreport || (os == 'linux' && debug)
[browser_privacypane_4.js]
skip-if = e10s # leaks windows
[browser_privacypane_5.js]
skip-if = e10s # leaks windows
[browser_privacypane_8.js]

View File

@ -18,14 +18,10 @@ function test() {
test_custom_retention("acceptCookies", "remember"),
test_custom_retention("acceptCookies", "custom")
],
(runtime.isReleaseBuild ? [
[
test_custom_retention("acceptThirdPartyMenu", "remember", "visited"),
test_custom_retention("acceptThirdPartyMenu", "custom", "always")
]
: [
test_custom_retention("acceptThirdPartyMenu", "remember", "always"),
test_custom_retention("acceptThirdPartyMenu", "custom", "visited")
]), [
], [
test_custom_retention("keepCookiesUntil", "remember", 1),
test_custom_retention("keepCookiesUntil", "custom", 2),
test_custom_retention("keepCookiesUntil", "custom", 0),

View File

@ -1,6 +1,5 @@
/* 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/. */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
@ -14,13 +13,13 @@ function test() {
loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
run_test_subset([
test_locbar_suggestion_retention(-1, undefined),
test_locbar_suggestion_retention(1, -1),
test_locbar_suggestion_retention(2, 1),
test_locbar_suggestion_retention(0, 2),
test_locbar_suggestion_retention(0, 0),
test_locbar_suggestion_retention("history", true),
test_locbar_suggestion_retention("bookmark", true),
test_locbar_suggestion_retention("openpage", false),
test_locbar_suggestion_retention("history", true),
test_locbar_suggestion_retention("history", false),
// reset all preferences to their default values once we're done
reset_preferences
]);
}
}

View File

@ -310,18 +310,14 @@ function test_custom_retention(controlToChange, expect, valueIncrement) {
};
}
function test_locbar_suggestion_retention(mode, expect) {
function test_locbar_suggestion_retention(suggestion, autocomplete) {
return function(win) {
let locbarsuggest = win.document.getElementById("locationBarSuggestion");
ok(locbarsuggest, "location bar suggestion menulist should exist");
let elem = win.document.getElementById(suggestion + "Suggestion");
ok(elem, "Suggest " + suggestion + " checkbox should exist.");
elem.click();
if (expect !== undefined) {
is(locbarsuggest.value, expect,
"location bar suggestion is expected to remain " + expect);
}
locbarsuggest.value = mode;
controlChanged(locbarsuggest);
is(Services.prefs.getBoolPref("browser.urlbar.autocomplete.enabled"), autocomplete,
"browser.urlbar.autocomplete.enabled pref should be " + autocomplete);
};
}

View File

@ -0,0 +1,58 @@
/* 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/. */
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
var stringBundle = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService)
.createBundle("chrome://browser/locale/aboutPrivateBrowsing.properties");
if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
document.title = stringBundle.GetStringFromName("title.normal");
setFavIcon("chrome://global/skin/icons/question-16.png");
} else {
document.title = stringBundle.GetStringFromName("title");
setFavIcon("chrome://browser/skin/Privacy-16.png");
}
var mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
function setFavIcon(url) {
var icon = document.createElement("link");
icon.setAttribute("rel", "icon");
icon.setAttribute("type", "image/png");
icon.setAttribute("href", url);
var head = document.getElementsByTagName("head")[0];
head.insertBefore(icon, head.firstChild);
}
document.addEventListener("DOMContentLoaded", function () {
if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
document.body.setAttribute("class", "normal");
}
// Set up the help link
let learnMoreURL = Cc["@mozilla.org/toolkit/URLFormatterService;1"]
.getService(Ci.nsIURLFormatter)
.formatURLPref("app.support.baseURL");
let learnMore = document.getElementById("learnMore");
if (learnMore) {
learnMore.setAttribute("href", learnMoreURL + "private-browsing");
}
let startPrivateBrowsing = document.getElementById("startPrivateBrowsing");
if (startPrivateBrowsing) {
startPrivateBrowsing.addEventListener("command", openPrivateWindow);
}
}, false);
function openPrivateWindow() {
mainWindow.OpenBrowserWindow({private: true});
}

View File

@ -20,56 +20,7 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rel="stylesheet" href="chrome://browser/content/aboutPrivateBrowsing.css" type="text/css" media="all"/>
<script type="application/javascript;version=1.7"><![CDATA[
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
document.title = "]]>&aboutPrivateBrowsing.title.normal;<![CDATA[";
setFavIcon("chrome://global/skin/icons/question-16.png");
} else {
#ifndef XP_MACOSX
document.title = "]]>&aboutPrivateBrowsing.title;<![CDATA[";
#endif
setFavIcon("chrome://browser/skin/Privacy-16.png");
}
var mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
function setFavIcon(url) {
var icon = document.createElement("link");
icon.setAttribute("rel", "icon");
icon.setAttribute("type", "image/png");
icon.setAttribute("href", url);
var head = document.getElementsByTagName("head")[0];
head.insertBefore(icon, head.firstChild);
}
document.addEventListener("DOMContentLoaded", function () {
if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
document.body.setAttribute("class", "normal");
}
// Set up the help link
let learnMoreURL = Cc["@mozilla.org/toolkit/URLFormatterService;1"]
.getService(Ci.nsIURLFormatter)
.formatURLPref("app.support.baseURL");
let learnMore = document.getElementById("learnMore");
if (learnMore) {
learnMore.setAttribute("href", learnMoreURL + "private-browsing");
}
}, false);
function openPrivateWindow() {
mainWindow.OpenBrowserWindow({private: true});
}
]]></script>
<script type="application/javascript;version=1.7" src="chrome://browser/content/aboutPrivateBrowsing.js"></script>
</head>
<body dir="&locale.dir;" class="private">
@ -85,10 +36,10 @@
<p class="notPrivateText showNormal">&aboutPrivateBrowsing.notPrivate;</p>
<button xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
id="startPrivateBrowsing"
class="openPrivate showNormal"
label="&privatebrowsingpage.openPrivateWindow.label;"
accesskey="&privatebrowsingpage.openPrivateWindow.accesskey;"
oncommand="openPrivateWindow();"/>
accesskey="&privatebrowsingpage.openPrivateWindow.accesskey;"/>
<div class="showPrivate">
<p class="moreInfoText">&aboutPrivateBrowsing.moreInfo;</p>
<p><a id="learnMore" target="_blank">&aboutPrivateBrowsing.learnMore;</a></p>

View File

@ -4,4 +4,5 @@
browser.jar:
* content/browser/aboutPrivateBrowsing.css (content/aboutPrivateBrowsing.css)
* content/browser/aboutPrivateBrowsing.xhtml (content/aboutPrivateBrowsing.xhtml)
content/browser/aboutPrivateBrowsing.xhtml (content/aboutPrivateBrowsing.xhtml)
* content/browser/aboutPrivateBrowsing.js (content/aboutPrivateBrowsing.js)

View File

@ -27,7 +27,7 @@ function test() {
about_pb_title = "Open a private window?";
pb_page_with_title = test_title + " - (Private Browsing)";
pb_page_without_title = app_name + " - (Private Browsing)";
pb_about_pb_title = pb_page_without_title;
pb_about_pb_title = "You're browsing privately - (Private Browsing)";
}
else {
page_with_title = test_title + " - " + app_name;

View File

@ -539,12 +539,12 @@ StyleEditorUI.prototype = {
editor.removeAllUnusedRegions();
if (data.reports.length > 0) {
// So there is some coverage markup, but can we apply it?
let text = editor.sourceEditor.getText() + "\r";
// If the CSS text contains a '}' with some non-whitespace
// after then we assume this is compressed CSS and stop
// marking-up.
if (!/}\s*\S+\s*\r/.test(text)) {
// Only apply if this file isn't compressed. We detect a
// compressed file if there are more rules than lines.
let text = editor.sourceEditor.getText();
let lineCount = text.split("\n").length;
let ruleCount = editor.styleSheet.ruleCount;
if (lineCount >= ruleCount) {
editor.addUnusedRegions(data.reports);
}
else {

View File

@ -0,0 +1,2 @@
title=You're browsing privately
title.normal=Open a private window?

View File

@ -16,12 +16,12 @@
<!ENTITY locationBar.label "Location Bar">
<!ENTITY locbar.pre.label "When using the location bar, suggest:">
<!ENTITY locbar.pre.accessKey "u">
<!ENTITY locbar.post.label "">
<!ENTITY locbar.both.label "History and Bookmarks">
<!ENTITY locbar.history.label "History">
<!ENTITY locbar.history.accesskey "i">
<!ENTITY locbar.bookmarks.label "Bookmarks">
<!ENTITY locbar.nothing.label "Nothing">
<!ENTITY locbar.bookmarks.accesskey "d">
<!ENTITY locbar.openpage.label "Open tabs">
<!ENTITY locbar.openpage.accesskey "g">
<!ENTITY acceptCookies.label "Accept cookies from sites">
<!ENTITY acceptCookies.accesskey "A">

View File

@ -10,6 +10,7 @@
locale/browser/aboutCertError.dtd (%chrome/browser/aboutCertError.dtd)
locale/browser/aboutDialog.dtd (%chrome/browser/aboutDialog.dtd)
locale/browser/aboutPrivateBrowsing.dtd (%chrome/browser/aboutPrivateBrowsing.dtd)
locale/browser/aboutPrivateBrowsing.properties (%chrome/browser/aboutPrivateBrowsing.properties)
locale/browser/aboutRobots.dtd (%chrome/browser/aboutRobots.dtd)
locale/browser/aboutHome.dtd (%chrome/browser/aboutHome.dtd)
#ifdef MOZ_SERVICES_HEALTHREPORT

View File

@ -305,14 +305,21 @@ pref("browser.search.official", true);
// enable xul error pages
pref("browser.xul.error_pages.enabled", true);
// Specify emptyRestriction = 0 so that bookmarks appear in the list by default
pref("browser.urlbar.default.behavior", 0);
pref("browser.urlbar.default.behavior.emptyRestriction", 0);
// Let the faviconservice know that we display favicons as 25x25px so that it
// uses the right size when optimizing favicons
pref("places.favicons.optimizeToDimension", 25);
// The default behavior for the urlbar can be configured to use any combination
// of the match filters with each additional filter adding more results (union).
pref("browser.urlbar.suggest.history", true);
pref("browser.urlbar.suggest.bookmark", true);
pref("browser.urlbar.suggest.openpage", true);
pref("browser.urlbar.suggest.search", true);
// Restrictions to current suggestions can also be applied (intersection).
// Typed suggestion works only if history is set to true.
pref("browser.urlbar.suggest.history.onlyTyped", false);
// various and sundry awesomebar prefs (should remove/re-evaluate
// these once bug 447900 is fixed)
pref("browser.urlbar.trimURLs", true);

View File

@ -300,6 +300,12 @@ HiddenBrowser.prototype = {
this._applySize();
doc.getElementById("win").appendChild(this._browser);
// The browser might not have a docShell here if the HostFrame was
// destroyed while the promise was resolved. Simply bail out.
if (!this._browser.docShell) {
return;
}
// Let the docShell be inactive so that document.hidden=true.
this._browser.docShell.isActive = false;

View File

@ -14768,6 +14768,25 @@ class CGEventMethod(CGNativeMember):
target += ".SetValue()"
source += ".Value()"
members += sequenceCopy % (target, source)
elif m.type.isSpiderMonkeyInterface():
srcname = "%s.%s" % (self.args[1].name, name)
if m.type.nullable():
members += fill(
"""
if (${srcname}.IsNull()) {
e->${varname} = nullptr;
} else {
e->${varname} = ${srcname}.Value().Obj();
}
""",
varname=name,
srcname=srcname);
else:
members += fill(
"""
e->${varname}.set(${srcname}.Obj());
""",
varname=name, srcname=srcname);
else:
members += "e->%s = %s.%s;\n" % (name, self.args[1].name, name)
if m.type.isAny() or m.type.isObject() or m.type.isSpiderMonkeyInterface():

View File

@ -1357,6 +1357,25 @@ CanvasRenderingContext2D::ClearTarget()
state->colorStyles[Style::FILL] = NS_RGB(0,0,0);
state->colorStyles[Style::STROKE] = NS_RGB(0,0,0);
state->shadowColor = NS_RGBA(0,0,0,0);
// For vertical writing-mode, unless text-orientation is sideways,
// we'll modify the initial value of textBaseline to 'middle'.
nsRefPtr<nsStyleContext> canvasStyle;
if (mCanvasElement && mCanvasElement->IsInDoc()) {
nsCOMPtr<nsIPresShell> presShell = GetPresShell();
if (presShell) {
canvasStyle =
nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement,
nullptr,
presShell);
if (canvasStyle) {
WritingMode wm(canvasStyle);
if (wm.IsVertical() && !wm.IsSideways()) {
state->textBaseline = TextBaseline::MIDDLE;
}
}
}
}
}
NS_IMETHODIMP
@ -3149,6 +3168,7 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcess
gfxPoint point = mPt;
bool rtl = mTextRun->IsRightToLeft();
bool verticalRun = mTextRun->IsVertical();
bool centerBaseline = mTextRun->UseCenterBaseline();
gfxFloat& inlineCoord = verticalRun ? point.y : point.x;
inlineCoord += xOffset;
@ -3217,20 +3237,27 @@ struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcess
if (runs[c].mOrientation ==
gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT) {
sidewaysRestore.Init(mCtx->mTarget);
// TODO: The baseline adjustment here is kinda ad-hoc; eventually
// perhaps we should check for horizontal and vertical baseline data
// in the font, and adjust accordingly.
// (The same will be true for HTML text layout.)
const gfxFont::Metrics& metrics = mTextRun->GetFontGroup()->
GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal);
mCtx->mTarget->SetTransform(mCtx->mTarget->GetTransform().Copy().
gfx::Matrix mat = mCtx->mTarget->GetTransform().Copy().
PreTranslate(baselineOrigin). // translate origin for rotation
PreRotate(gfx::Float(M_PI / 2.0)). // turn 90deg clockwise
PreTranslate(-baselineOrigin). // undo the translation
PreTranslate(Point(0, (metrics.emAscent - metrics.emDescent) / 2)));
// and offset the (alphabetic) baseline of the
PreTranslate(-baselineOrigin); // undo the translation
if (centerBaseline) {
// TODO: The baseline adjustment here is kinda ad hoc; eventually
// perhaps we should check for horizontal and vertical baseline data
// in the font, and adjust accordingly.
// (The same will be true for HTML text layout.)
float offset = (metrics.emAscent - metrics.emDescent) / 2;
mat = mat.PreTranslate(Point(0, offset));
// offset the (alphabetic) baseline of the
// horizontally-shaped text from the (centered)
// default baseline used for vertical
}
mCtx->mTarget->SetTransform(mat);
}
RefPtr<GlyphRenderingOptions> renderingOptions = font->GetGlyphRenderingOptions();
@ -3522,39 +3549,45 @@ CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText,
processor.mPt.x -= anchorX * totalWidth;
// offset pt.y based on text baseline
// offset pt.y (or pt.x, for vertical text) based on text baseline
processor.mFontgrp->UpdateUserFonts(); // ensure user font generation is current
const gfxFont::Metrics& fontMetrics =
processor.mFontgrp->GetFirstValidFont()->GetMetrics(
((processor.mTextRunFlags & gfxTextRunFactory::TEXT_ORIENT_MASK) ==
gfxTextRunFactory::TEXT_ORIENT_HORIZONTAL)
? gfxFont::eHorizontal : gfxFont::eVertical);
processor.mFontgrp->GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal);
gfxFloat anchorY;
gfxFloat baselineAnchor;
switch (state.textBaseline)
{
case TextBaseline::HANGING:
// fall through; best we can do with the information available
case TextBaseline::TOP:
anchorY = fontMetrics.emAscent;
baselineAnchor = fontMetrics.emAscent;
break;
case TextBaseline::MIDDLE:
anchorY = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
baselineAnchor = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
break;
case TextBaseline::IDEOGRAPHIC:
// fall through; best we can do with the information available
case TextBaseline::ALPHABETIC:
anchorY = 0;
baselineAnchor = 0;
break;
case TextBaseline::BOTTOM:
anchorY = -fontMetrics.emDescent;
baselineAnchor = -fontMetrics.emDescent;
break;
default:
MOZ_CRASH("unexpected TextBaseline");
}
processor.mPt.y += anchorY;
if (processor.mTextRun->IsVertical()) {
if (processor.mTextRun->UseCenterBaseline()) {
// Adjust to account for mTextRun being shaped using center baseline
// rather than alphabetic.
baselineAnchor -= (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
}
processor.mPt.x -= baselineAnchor;
} else {
processor.mPt.y += baselineAnchor;
}
// correct bounding box to get it to be the correct size/position
processor.mBoundingBox.width = totalWidth;

View File

@ -1793,28 +1793,31 @@ HTMLMediaElement::CaptureStreamInternal(bool aFinishWhenEnded)
}
#endif
OutputMediaStream* out = mOutputStreams.AppendElement();
#ifdef DEBUG
// Estimate hints based on the type of the media element
// under the preference media.capturestream_hints for the
// debug builds only. This allows WebRTC Peer Connection
// to behave appropriately when media streams generated
// via mozCaptureStream*() are added to the Peer Connection.
// This functionality is planned to be used as part of Audio
// Quality Performance testing for WebRTC.
// Bug932845: Revisit this once hints mechanism is dealt with
// holistically.
uint8_t hints = 0;
if (Preferences::GetBool("media.capturestream_hints.enabled")) {
if (IsVideo() && GetVideoFrameContainer()) {
hints = DOMMediaStream::HINT_CONTENTS_VIDEO | DOMMediaStream::HINT_CONTENTS_AUDIO;
} else {
hints = DOMMediaStream::HINT_CONTENTS_AUDIO;
if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_METADATA) {
hints = (mHasAudio? DOMMediaStream::HINT_CONTENTS_AUDIO : 0) |
(mHasVideo? DOMMediaStream::HINT_CONTENTS_VIDEO : 0);
} else {
#ifdef DEBUG
// Estimate hints based on the type of the media element
// under the preference media.capturestream_hints for the
// debug builds only. This allows WebRTC Peer Connection
// to behave appropriately when media streams generated
// via mozCaptureStream*() are added to the Peer Connection.
// This functionality is planned to be used as part of Audio
// Quality Performance testing for WebRTC.
// Bug932845: Revisit this once hints mechanism is dealt with
// holistically.
if (Preferences::GetBool("media.capturestream_hints.enabled")) {
if (IsVideo() && GetVideoFrameContainer()) {
hints = DOMMediaStream::HINT_CONTENTS_VIDEO | DOMMediaStream::HINT_CONTENTS_AUDIO;
} else {
hints = DOMMediaStream::HINT_CONTENTS_AUDIO;
}
}
#endif
}
out->mStream = DOMMediaStream::CreateTrackUnionStream(window, hints);
#else
out->mStream = DOMMediaStream::CreateTrackUnionStream(window);
#endif
nsRefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
out->mStream->CombineWithPrincipal(principal);
out->mFinishWhenEnded = aFinishWhenEnded;

View File

@ -8734,16 +8734,15 @@ FileManager::Invalidate()
{
public:
static PLDHashOperator
CopyToTArray(const uint64_t& aKey, FileInfo* aValue, void* aUserArg)
ClearDBRefs(const uint64_t& aKey, FileInfo*& aValue, void* aUserArg)
{
MOZ_ASSERT(aValue);
auto* array = static_cast<FallibleTArray<FileInfo*>*>(aUserArg);
MOZ_ASSERT(array);
if (aValue->LockedClearDBRefs()) {
return PL_DHASH_NEXT;
}
MOZ_ALWAYS_TRUE(array->AppendElement(aValue));
return PL_DHASH_NEXT;
return PL_DHASH_REMOVE;
}
};
@ -8752,26 +8751,12 @@ FileManager::Invalidate()
return NS_ERROR_UNEXPECTED;
}
FallibleTArray<FileInfo*> fileInfos;
{
MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
MOZ_ASSERT(!mInvalidated);
mInvalidated = true;
MOZ_ASSERT(!mInvalidated);
mInvalidated = true;
if (NS_WARN_IF(!fileInfos.SetCapacity(mFileInfos.Count()))) {
return NS_ERROR_OUT_OF_MEMORY;
}
mFileInfos.EnumerateRead(Helper::CopyToTArray, &fileInfos);
}
for (uint32_t count = fileInfos.Length(), index = 0; index < count; index++) {
FileInfo* fileInfo = fileInfos[index];
MOZ_ASSERT(fileInfo);
fileInfo->ClearDBRefs();
}
mFileInfos.Enumerate(Helper::ClearDBRefs, nullptr);
return NS_OK;
}

View File

@ -130,15 +130,13 @@ FileInfo::GetReferences(int32_t* aRefCnt,
void
FileInfo::UpdateReferences(ThreadSafeAutoRefCnt& aRefCount,
int32_t aDelta,
bool aClear)
int32_t aDelta)
{
// XXX This can go away once DOM objects no longer hold FileInfo objects...
// Looking at you, IDBMutableFile...
if (IndexedDatabaseManager::IsClosed()) {
MOZ_ASSERT(&aRefCount == &mRefCnt);
MOZ_ASSERT(aDelta == 1 || aDelta == -1);
MOZ_ASSERT(!aClear);
if (aDelta > 0) {
++aRefCount;
@ -158,7 +156,7 @@ FileInfo::UpdateReferences(ThreadSafeAutoRefCnt& aRefCount,
{
MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
aRefCount = aClear ? 0 : aRefCount + aDelta;
aRefCount = aRefCount + aDelta;
if (mRefCnt + mDBRefCnt + mSliceRefCnt > 0) {
return;
@ -176,6 +174,29 @@ FileInfo::UpdateReferences(ThreadSafeAutoRefCnt& aRefCount,
delete this;
}
bool
FileInfo::LockedClearDBRefs()
{
MOZ_ASSERT(!IndexedDatabaseManager::IsClosed());
IndexedDatabaseManager::FileMutex().AssertCurrentThreadOwns();
mDBRefCnt = 0;
if (mRefCnt || mSliceRefCnt) {
return true;
}
// In this case, we are not responsible for removing the file info from the
// hashtable. It's up to FileManager which is the only caller of this method.
MOZ_ASSERT(mFileManager->Invalidated());
delete this;
return false;
}
void
FileInfo::Cleanup()
{

View File

@ -50,12 +50,6 @@ public:
UpdateReferences(mDBRefCnt, aDelta);
}
void
ClearDBRefs()
{
UpdateReferences(mDBRefCnt, 0, true);
}
void
UpdateSliceRefs(int32_t aDelta)
{
@ -80,8 +74,10 @@ protected:
private:
void
UpdateReferences(ThreadSafeAutoRefCnt& aRefCount,
int32_t aDelta,
bool aClear = false);
int32_t aDelta);
bool
LockedClearDBRefs();
void
Cleanup();

View File

@ -158,6 +158,10 @@ OpusTrackEncoder::Init(int aChannels, int aSamplingRate)
// let InterleaveTrackData downmix pcm data.
mChannels = aChannels > MAX_CHANNELS ? MAX_CHANNELS : aChannels;
// Reject non-audio sample rates.
NS_ENSURE_TRUE(aSamplingRate >= 8000, NS_ERROR_INVALID_ARG);
NS_ENSURE_TRUE(aSamplingRate <= 192000, NS_ERROR_INVALID_ARG);
// According to www.opus-codec.org, creating an opus encoder requires the
// sampling rate of source signal be one of 8000, 12000, 16000, 24000, or
// 48000. If this constraint is not satisfied, we resample the input to 48kHz.

View File

@ -49,10 +49,10 @@ VorbisTrackEncoder::~VorbisTrackEncoder()
nsresult
VorbisTrackEncoder::Init(int aChannels, int aSamplingRate)
{
if (aChannels <= 0 || aChannels > 8) {
VORBISLOG("aChannels <= 0 || aChannels > 8");
return NS_ERROR_INVALID_ARG;
}
NS_ENSURE_TRUE(aChannels > 0, NS_ERROR_INVALID_ARG);
NS_ENSURE_TRUE(aChannels <= 8, NS_ERROR_INVALID_ARG);
NS_ENSURE_TRUE(aSamplingRate >= 8000, NS_ERROR_INVALID_ARG);
NS_ENSURE_TRUE(aSamplingRate <= 192000, NS_ERROR_INVALID_ARG);
// This monitor is used to wake up other methods that are waiting for encoder
// to be completely initialized.

View File

@ -9,7 +9,6 @@
#include "MP4Reader.h"
#include "MP4Decoder.h"
#include "mozilla/RefPtr.h"
#include "mozilla/ReentrantMonitor.h"
#include "mp4_demuxer/Adts.h"
#include "mp4_demuxer/DecoderData.h"
#include "nsIThread.h"
@ -33,10 +32,11 @@ AppleATDecoder::AppleATDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
, mCallback(aCallback)
, mConverter(nullptr)
, mStream(nullptr)
, mCurrentAudioTimestamp(0)
, mCurrentAudioTimestamp(-1)
, mNextAudioTimestamp(-1)
, mSamplePosition(0)
, mHaveOutput(false)
, mFlushed(false)
, mSizeDecoded(0)
, mLastError(noErr)
{
MOZ_COUNT_CTOR(AppleATDecoder);
LOG("Creating Apple AudioToolbox decoder");
@ -238,8 +238,7 @@ AppleATDecoder::SampleCallback(uint32_t aNumBytes,
// Pick a multiple of the frame size close to a power of two
// for efficient allocation.
const uint32_t MAX_AUDIO_FRAMES = 128;
const uint32_t decodedSize = MAX_AUDIO_FRAMES * mConfig.channel_count *
sizeof(AudioDataValue);
const uint32_t maxDecodedSamples = MAX_AUDIO_FRAMES * mConfig.channel_count;
// Descriptions for _decompressed_ audio packets. ignored.
nsAutoArrayPtr<AudioStreamPacketDescription>
@ -251,14 +250,15 @@ AppleATDecoder::SampleCallback(uint32_t aNumBytes,
PassthroughUserData userData =
{ this, aNumPackets, aNumBytes, aData, aPackets, false };
do {
// Decompressed audio buffer
nsAutoArrayPtr<uint8_t> decoded(new uint8_t[decodedSize]);
// Decompressed audio buffer
nsAutoArrayPtr<AudioDataValue> decoded(new AudioDataValue[maxDecodedSamples]);
do {
AudioBufferList decBuffer;
decBuffer.mNumberBuffers = 1;
decBuffer.mBuffers[0].mNumberChannels = mOutputFormat.mChannelsPerFrame;
decBuffer.mBuffers[0].mDataByteSize = decodedSize;
decBuffer.mBuffers[0].mDataByteSize =
maxDecodedSamples * sizeof(AudioDataValue);
decBuffer.mBuffers[0].mData = decoded.get();
// in: the max number of packets we can handle from the decoder.
@ -274,50 +274,28 @@ AppleATDecoder::SampleCallback(uint32_t aNumBytes,
if (rv && rv != kNeedMoreData) {
LOG("Error decoding audio stream: %d\n", rv);
mCallback->Error();
break;
}
LOG("%d frames decoded", numFrames);
// If we decoded zero frames then AudioConverterFillComplexBuffer is out
// of data to provide. We drained its internal buffer completely on the
// last pass.
if (numFrames == 0 && rv == kNeedMoreData) {
LOG("FillComplexBuffer out of data exactly\n");
mCallback->InputExhausted();
mLastError = rv;
break;
}
const int rate = mOutputFormat.mSampleRate;
const int channels = mOutputFormat.mChannelsPerFrame;
int64_t time = mCurrentAudioTimestamp;
int64_t duration = FramesToUsecs(numFrames, rate).value();
LOG("pushed audio at time %lfs; duration %lfs\n",
(double)time / USECS_PER_S, (double)duration / USECS_PER_S);
AudioData* audio = new AudioData(mSamplePosition,
time, duration, numFrames,
reinterpret_cast<AudioDataValue*>(decoded.forget()),
channels, rate);
mCallback->Output(audio);
mHaveOutput = true;
mOutputData.AppendElements(decoded.get(),
numFrames * mConfig.channel_count);
if (rv == kNeedMoreData) {
// No error; we just need more data.
LOG("FillComplexBuffer out of data\n");
mCallback->InputExhausted();
break;
}
LOG("%d frames decoded", numFrames);
} while (true);
mSizeDecoded += aNumBytes;
}
void
AppleATDecoder::SetupDecoder()
{
LOG("Setting up Apple AudioToolbox decoder.");
mHaveOutput = false;
AudioStreamBasicDescription inputFormat;
nsresult rv = AppleUtils::GetRichestDecodableFormat(mStream, inputFormat);
@ -368,23 +346,84 @@ AppleATDecoder::SubmitSample(nsAutoPtr<mp4_demuxer::MP4Sample> aSample)
return;
}
}
// Push the sample to the AudioFileStream for parsing.
mSamplePosition = aSample->byte_offset;
mCurrentAudioTimestamp = aSample->composition_timestamp;
uint32_t flags = mFlushed ? kAudioFileStreamParseFlag_Discontinuity : 0;
const Microseconds fuzz = 5;
CheckedInt<Microseconds> upperFuzz = mNextAudioTimestamp + fuzz;
CheckedInt<Microseconds> lowerFuzz = mNextAudioTimestamp - fuzz;
bool discontinuity =
!mNextAudioTimestamp.isValid() || mNextAudioTimestamp.value() < 0 ||
!upperFuzz.isValid() || lowerFuzz.value() < 0 ||
upperFuzz.value() < aSample->composition_timestamp ||
lowerFuzz.value() > aSample->composition_timestamp;
if (discontinuity) {
LOG("Discontinuity detected, expected %lld got %lld\n",
mNextAudioTimestamp.value(), aSample->composition_timestamp);
mCurrentAudioTimestamp = aSample->composition_timestamp;
mSamplePosition = aSample->byte_offset;
}
uint32_t flags = discontinuity ? kAudioFileStreamParseFlag_Discontinuity : 0;
OSStatus rv = AudioFileStreamParseBytes(mStream,
aSample->size,
aSample->data,
flags);
if (!mOutputData.IsEmpty()) {
int rate = mOutputFormat.mSampleRate;
int channels = mOutputFormat.mChannelsPerFrame;
size_t numFrames = mOutputData.Length() / channels;
CheckedInt<Microseconds> duration = FramesToUsecs(numFrames, rate);
if (!duration.isValid()) {
NS_ERROR("Invalid count of accumulated audio samples");
mCallback->Error();
return;
}
LOG("pushed audio at time %lfs; duration %lfs\n",
(double)mCurrentAudioTimestamp.value() / USECS_PER_S,
(double)duration.value() / USECS_PER_S);
nsAutoArrayPtr<AudioDataValue>
data(new AudioDataValue[mOutputData.Length()]);
PodCopy(data.get(), &mOutputData[0], mOutputData.Length());
mOutputData.Clear();
AudioData* audio = new AudioData(mSamplePosition,
mCurrentAudioTimestamp.value(),
duration.value(),
numFrames,
data.forget(),
channels,
rate);
mCallback->Output(audio);
mCurrentAudioTimestamp += duration.value();
if (!mCurrentAudioTimestamp.isValid()) {
NS_ERROR("Invalid count of accumulated audio samples");
mCallback->Error();
return;
}
mSamplePosition += mSizeDecoded;
mSizeDecoded = 0;
}
// This is the timestamp of the next sample we should be receiving
mNextAudioTimestamp =
CheckedInt<Microseconds>(aSample->composition_timestamp) + aSample->duration;
if (rv != noErr) {
LOG("Error %d parsing audio data", rv);
mCallback->Error();
return;
}
if (mLastError != noErr) {
LOG("Error %d during decoding", mLastError);
mCallback->Error();
mLastError = noErr;
return;
}
// Sometimes we need multiple input samples before AudioToolbox
// starts decoding. If we haven't seen any output yet, ask for
// more data here.
if (!mHaveOutput) {
if (mTaskQueue->IsEmpty()) {
mCallback->InputExhausted();
}
}
@ -392,7 +431,9 @@ AppleATDecoder::SubmitSample(nsAutoPtr<mp4_demuxer::MP4Sample> aSample)
void
AppleATDecoder::SignalFlush()
{
mFlushed = true;
mOutputData.Clear();
mNextAudioTimestamp = -1;
mSizeDecoded = 0;
}
} // namespace mozilla

View File

@ -49,12 +49,20 @@ private:
MediaDataDecoderCallback* mCallback;
AudioConverterRef mConverter;
AudioFileStreamID mStream;
Microseconds mCurrentAudioTimestamp;
// Timestamp of the next audio frame going to be output by the decoder.
CheckedInt<Microseconds> mCurrentAudioTimestamp;
// Estimated timestamp of the next compressed audio packet to be supplied by
// the MP4 demuxer.
CheckedInt<Microseconds> mNextAudioTimestamp;
int64_t mSamplePosition;
bool mHaveOutput;
bool mFlushed;
// Compressed data size that has been processed by the decoder since the last
// output.
int64_t mSizeDecoded;
AudioStreamBasicDescription mOutputFormat;
AudioFileTypeID mFileType;
// Array containing the queued decoded audio frames, about to be output.
nsTArray<AudioDataValue> mOutputData;
OSStatus mLastError;
void SetupDecoder();
void SubmitSample(nsAutoPtr<mp4_demuxer::MP4Sample> aSample);

View File

@ -18,6 +18,7 @@ namespace mozilla
{
bool FFmpegDataDecoder<LIBAV_VER>::sFFmpegInitDone = false;
StaticMutex FFmpegDataDecoder<LIBAV_VER>::sMonitor;
FFmpegDataDecoder<LIBAV_VER>::FFmpegDataDecoder(MediaTaskQueue* aTaskQueue,
AVCodecID aCodecID)
@ -58,6 +59,8 @@ ChoosePixelFormat(AVCodecContext* aCodecContext, const PixelFormat* aFormats)
nsresult
FFmpegDataDecoder<LIBAV_VER>::Init()
{
StaticMutexAutoLock mon(sMonitor);
FFMPEG_LOG("Initialising FFmpeg decoder.");
if (!sFFmpegInitDone) {
@ -130,6 +133,8 @@ FFmpegDataDecoder<LIBAV_VER>::Flush()
nsresult
FFmpegDataDecoder<LIBAV_VER>::Shutdown()
{
StaticMutexAutoLock mon(sMonitor);
if (sFFmpegInitDone) {
avcodec_close(mCodecContext);
av_freep(&mCodecContext);

View File

@ -10,6 +10,7 @@
#include "PlatformDecoderModule.h"
#include "FFmpegLibs.h"
#include "mozilla/Vector.h"
#include "mozilla/StaticMutex.h"
namespace mozilla
{
@ -44,6 +45,7 @@ protected:
private:
static bool sFFmpegInitDone;
static StaticMutex sMonitor;
AVCodecID mCodecID;
};

View File

@ -66,6 +66,15 @@ TEST(Media, OpusEncoder_Init)
// Expect false with 0 or negative sampling rate of input signal.
EXPECT_FALSE(TestOpusInit(1, 0));
EXPECT_FALSE(TestOpusInit(1, -1));
// Verify sample rate bounds checking.
EXPECT_FALSE(TestOpusInit(2, 2000));
EXPECT_FALSE(TestOpusInit(2, 4000));
EXPECT_FALSE(TestOpusInit(2, 7999));
EXPECT_TRUE(TestOpusInit(2, 8000));
EXPECT_TRUE(TestOpusInit(2, 192000));
EXPECT_FALSE(TestOpusInit(2, 192001));
EXPECT_FALSE(TestOpusInit(2, 200000));
}
TEST(Media, OpusEncoder_Resample)

View File

@ -130,6 +130,9 @@ TEST(VorbisTrackEncoder, Init)
// Sample rate and channel range test.
for (int i = 1; i <= 8; i++) {
EXPECT_FALSE(TestVorbisInit(i, -1));
EXPECT_FALSE(TestVorbisInit(i, 2000));
EXPECT_FALSE(TestVorbisInit(i, 4000));
EXPECT_FALSE(TestVorbisInit(i, 7999));
EXPECT_TRUE(TestVorbisInit(i, 8000));
EXPECT_TRUE(TestVorbisInit(i, 11000));
EXPECT_TRUE(TestVorbisInit(i, 16000));
@ -138,6 +141,8 @@ TEST(VorbisTrackEncoder, Init)
EXPECT_TRUE(TestVorbisInit(i, 44100));
EXPECT_TRUE(TestVorbisInit(i, 48000));
EXPECT_TRUE(TestVorbisInit(i, 96000));
EXPECT_TRUE(TestVorbisInit(i, 192000));
EXPECT_FALSE(TestVorbisInit(i, 192001));
EXPECT_FALSE(TestVorbisInit(i, 200000 + 1));
}
}

View File

@ -18,6 +18,8 @@ runWithMSE(function (ms, v) {
v.addEventListener("loadedmetadata", function () {
ok(true, "Got loadedmetadata event");
is(v.videoWidth, 320, "videoWidth has correct initial value");
is(v.videoHeight, 240, "videoHeight has correct initial value");
SimpleTest.finish();
});

View File

@ -89,6 +89,8 @@ skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
skip-if = toolkit == 'gonk' # b2g emulator seems to be too slow (Bug 1016498 and 1008080)
[test_peerConnection_bug1042791.html]
skip-if = buildapp == 'b2g' || os == 'android' # bug 1043403
[test_peerConnection_capturedVideo.html]
skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
[test_peerConnection_close.html]
skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
[test_peerConnection_errorCallbacks.html]

View File

@ -2354,10 +2354,11 @@ PeerConnectionWrapper.prototype = {
}
},
verifySdp : function PCW_verifySdp(desc, expectedType, constraints,
offerOptions, trickleIceCallback) {
verifySdp : function PCW_verifySdp(desc, expectedType, offerConstraintsList,
answerConstraintsList, offerOptions, trickleIceCallback) {
info("Examining this SessionDescription: " + JSON.stringify(desc));
info("constraints: " + JSON.stringify(constraints));
info("offerConstraintsList: " + JSON.stringify(offerConstraintsList));
info("answerConstraintsList: " + JSON.stringify(answerConstraintsList));
info("offerOptions: " + JSON.stringify(offerOptions));
ok(desc, "SessionDescription is not null");
is(desc.type, expectedType, "SessionDescription type is " + expectedType);
@ -2376,11 +2377,11 @@ PeerConnectionWrapper.prototype = {
}
//TODO: how can we check for absence/presence of m=application?
//TODO: how to handle media contraints + offer options
var audioTracks = this.countAudioTracksInMediaConstraint(constraints);
if (constraints.length === 0) {
audioTracks = this.audioInOfferOptions(offerOptions);
}
var audioTracks =
Math.max(this.countAudioTracksInMediaConstraint(offerConstraintsList),
this.countAudioTracksInMediaConstraint(answerConstraintsList)) ||
this.audioInOfferOptions(offerOptions);
info("expected audio tracks: " + audioTracks);
if (audioTracks == 0) {
ok(!desc.sdp.contains("m=audio"), "audio m-line is absent from SDP");
@ -2393,11 +2394,11 @@ PeerConnectionWrapper.prototype = {
}
//TODO: how to handle media contraints + offer options
var videoTracks = this.countVideoTracksInMediaConstraint(constraints);
if (constraints.length === 0) {
videoTracks = this.videoInOfferOptions(offerOptions);
}
var videoTracks =
Math.max(this.countVideoTracksInMediaConstraint(offerConstraintsList),
this.countVideoTracksInMediaConstraint(answerConstraintsList)) ||
this.videoInOfferOptions(offerOptions);
info("expected video tracks: " + videoTracks);
if (videoTracks == 0) {
ok(!desc.sdp.contains("m=video"), "video m-line is absent from SDP");

View File

@ -220,7 +220,7 @@ var commandsPeerConnection = [
'PC_LOCAL_SANE_LOCAL_SDP',
function (test) {
test.pcLocal.verifySdp(test._local_offer, "offer",
test._offer_constraints, test._offer_options,
test._offer_constraints, test._answer_constraints, test._offer_options,
function(trickle) {
test.pcLocal.localRequiresTrickleIce = trickle;
});
@ -231,7 +231,7 @@ var commandsPeerConnection = [
'PC_REMOTE_SANE_REMOTE_SDP',
function (test) {
test.pcRemote.verifySdp(test._local_offer, "offer",
test._offer_constraints, test._offer_options,
test._offer_constraints, test._answer_constraints, test._offer_options,
function (trickle) {
test.pcRemote.remoteRequiresTrickleIce = trickle;
});
@ -344,7 +344,7 @@ var commandsPeerConnection = [
'PC_REMOTE_SANE_LOCAL_SDP',
function (test) {
test.pcRemote.verifySdp(test._remote_answer, "answer",
test._answer_constraints, test._offer_options,
test._offer_constraints, test._answer_constraints, test._offer_options,
function (trickle) {
test.pcRemote.localRequiresTrickleIce = trickle;
});
@ -355,7 +355,7 @@ var commandsPeerConnection = [
'PC_LOCAL_SANE_REMOTE_SDP',
function (test) {
test.pcLocal.verifySdp(test._remote_answer, "answer",
test._answer_constraints, test._offer_options,
test._offer_constraints, test._answer_constraints, test._offer_options,
function (trickle) {
test.pcLocal.remoteRequiresTrickleIce = trickle;
});
@ -837,7 +837,7 @@ var commandsDataChannel = [
'PC_LOCAL_SANE_LOCAL_SDP',
function (test) {
test.pcLocal.verifySdp(test._local_offer, "offer",
test._offer_constraints, test._offer_options,
test._offer_constraints, test._answer_constraints, test._offer_options,
function(trickle) {
test.pcLocal.localRequiresTrickleIce = trickle;
});
@ -848,7 +848,7 @@ var commandsDataChannel = [
'PC_REMOTE_SANE_REMOTE_SDP',
function (test) {
test.pcRemote.verifySdp(test._local_offer, "offer",
test._offer_constraints, test._offer_options,
test._offer_constraints, test._answer_constraints, test._offer_options,
function (trickle) {
test.pcRemote.remoteRequiresTrickleIce = trickle;
});
@ -941,7 +941,7 @@ var commandsDataChannel = [
'PC_REMOTE_SANE_LOCAL_SDP',
function (test) {
test.pcRemote.verifySdp(test._remote_answer, "answer",
test._answer_constraints, test._offer_options,
test._offer_constraints, test._answer_constraints, test._offer_options,
function (trickle) {
test.pcRemote.localRequiresTrickleIce = trickle;
});
@ -952,7 +952,7 @@ var commandsDataChannel = [
'PC_LOCAL_SANE_REMOTE_SDP',
function (test) {
test.pcLocal.verifySdp(test._remote_answer, "answer",
test._answer_constraints, test._offer_options,
test._offer_constraints, test._answer_constraints, test._offer_options,
function (trickle) {
test.pcLocal.remoteRequiresTrickleIce = trickle;
});

View File

@ -0,0 +1,55 @@
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="head.js"></script>
<script type="application/javascript" src="mediaStreamPlayback.js"></script>
<script type="application/javascript" src="pc.js"></script>
<script type="application/javascript" src="templates.js"></script>
<script type="application/javascript" src="turnConfig.js"></script>
</head>
<body>
<video id="v1" src="../../test/vp9cake.webm" height="120" width="160" autoplay muted></video>
<pre id="test">
<script type="application/javascript;version=1.8">
createHTML({
bug: "1081409",
title: "Captured video-only over peer connection",
visible: true
});
var domLoaded = new Promise(r => addEventListener("DOMContentLoaded", e => r()));
var test;
var stream;
var waitUntil = func => new Promise(resolve => {
var ival = setInterval(() => func() && resolve(clearInterval(ival)), 200);
});
runNetworkTest(function() {
test = new PeerConnectionTest();
test.setOfferOptions({ offerToReceiveVideo: false,
offerToReceiveAudio: false });
test.chain.insertAfter("PC_LOCAL_GUM", [["PC_LOCAL_CAPTUREVIDEO", function (test) {
domLoaded
.then(() => waitUntil(() => v1.videoWidth > 0)) // TODO: Bug 1096723
.then(function() {
stream = v1.mozCaptureStreamUntilEnded();
is(stream.getTracks().length, 2, "Captured stream has 2 tracks");
stream.getTracks().forEach(tr => test.pcLocal._pc.addTrack(tr, stream));
test.pcLocal.constraints = [{ video: true, audio:true }]; // fool tests
test.next();
})
.catch(function(reason) {
ok(false, "unexpected failure: " + reason);
SimpleTest.finish();
});
}
]]);
test.chain.removeAfter("PC_REMOTE_CHECK_MEDIA_FLOW_PRESENT");
test.run();
});
</script>
</pre>
</body>
</html>

View File

@ -227,8 +227,14 @@ InternalSetAudioRoutesICS(SwitchState aState)
AUDIO_POLICY_DEVICE_STATE_AVAILABLE, "");
sHeadsetState |= AUDIO_DEVICE_OUT_WIRED_HEADPHONE;
} else if (aState == SWITCH_STATE_OFF) {
AudioSystem::setDeviceConnectionState(static_cast<audio_devices_t>(sHeadsetState),
AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
if (sHeadsetState & AUDIO_DEVICE_OUT_WIRED_HEADSET) {
AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADSET,
AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
}
if (sHeadsetState & AUDIO_DEVICE_OUT_WIRED_HEADPHONE) {
AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADPHONE,
AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
}
sHeadsetState = 0;
}
}

View File

@ -1902,22 +1902,28 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
gfxPoint p(aPt->x * aRunParams.devPerApp,
aPt->y * aRunParams.devPerApp);
const Metrics& metrics = GetMetrics(eHorizontal);
// Adjust the matrix to draw the (horizontally-shaped) textrun with
// 90-degree CW rotation, and adjust position so that the rotated
// horizontal text (which uses a standard alphabetic baseline) will
// Get a matrix we can use to draw the (horizontally-shaped) textrun
// with 90-degree CW rotation.
gfxMatrix mat = aRunParams.context->CurrentMatrix().
Translate(p). // translate origin for rotation
Rotate(M_PI / 2.0). // turn 90deg clockwise
Translate(-p); // undo the translation
// If we're drawing rotated horizontal text for an element styled
// text-orientation:mixed, the dominant baseline will be vertical-
// centered. So in this case, we need to adjust the position so that
// the rotated horizontal text (which uses an alphabetic baseline) will
// look OK when juxtaposed with upright glyphs (rendered on a centered
// vertical baseline). The adjustment here is somewhat ad hoc; we
// should eventually look for baseline tables[1] in the fonts and use
// those if available.
// [1] http://www.microsoft.com/typography/otspec/base.htm
aRunParams.context->SetMatrix(aRunParams.context->CurrentMatrix().
Translate(p). // translate origin for rotation
Rotate(M_PI / 2.0). // turn 90deg clockwise
Translate(-p). // undo the translation
Translate(gfxPoint(0, (metrics.emAscent - metrics.emDescent) / 2)));
// and offset the (alphabetic) baseline of the
// horizontally-shaped text from the (centered)
// default baseline used for vertical
// [1] See http://www.microsoft.com/typography/otspec/base.htm
if (aTextRun->UseCenterBaseline()) {
gfxPoint baseAdj(0, (metrics.emAscent - metrics.emDescent) / 2);
mat.Translate(baseAdj);
}
aRunParams.context->SetMatrix(mat);
}
nsAutoPtr<gfxTextContextPaint> contextPaint;
@ -2138,15 +2144,33 @@ gfxFont::Measure(gfxTextRun *aTextRun,
// Current position in appunits
gfxFont::Orientation orientation =
aOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT
? gfxFont::eVertical : gfxFont::eHorizontal;
? eVertical : eHorizontal;
const gfxFont::Metrics& fontMetrics = GetMetrics(orientation);
gfxFloat baselineOffset = 0;
if (aTextRun->UseCenterBaseline() && orientation == eHorizontal) {
// For a horizontal font being used in vertical writing mode with
// text-orientation:mixed, the overall metrics we're accumulating
// will be aimed at a center baseline. But this font's metrics were
// based on the alphabetic baseline. So we compute a baseline offset
// that will be applied to ascent/descent values and glyph rects
// to effectively shift them relative to the baseline.
// XXX Eventually we should probably use the BASE table, if present.
// But it usually isn't, so we need an ad hoc adjustment for now.
baselineOffset = appUnitsPerDevUnit *
(fontMetrics.emAscent - fontMetrics.emDescent) / 2;
}
RunMetrics metrics;
metrics.mAscent = fontMetrics.maxAscent*appUnitsPerDevUnit;
metrics.mDescent = fontMetrics.maxDescent*appUnitsPerDevUnit;
metrics.mAscent = fontMetrics.maxAscent * appUnitsPerDevUnit;
metrics.mDescent = fontMetrics.maxDescent * appUnitsPerDevUnit;
if (aStart == aEnd) {
// exit now before we look at aSpacing[0], which is undefined
metrics.mBoundingBox = gfxRect(0, -metrics.mAscent, 0, metrics.mAscent + metrics.mDescent);
metrics.mAscent -= baselineOffset;
metrics.mDescent += baselineOffset;
metrics.mBoundingBox = gfxRect(0, -metrics.mAscent,
0, metrics.mAscent + metrics.mDescent);
return metrics;
}
@ -2247,6 +2271,12 @@ gfxFont::Measure(gfxTextRun *aTextRun,
metrics.mBoundingBox -= gfxPoint(x, 0);
}
if (baselineOffset != 0) {
metrics.mAscent -= baselineOffset;
metrics.mDescent += baselineOffset;
metrics.mBoundingBox.y += baselineOffset;
}
metrics.mAdvanceWidth = x*direction;
return metrics;
}

View File

@ -945,6 +945,12 @@ public:
gfxTextRunFactory::TEXT_ORIENT_HORIZONTAL;
}
bool UseCenterBaseline() const {
uint32_t orient = GetFlags() & gfxTextRunFactory::TEXT_ORIENT_MASK;
return orient == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED ||
orient == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
}
bool IsRightToLeft() const {
return (GetFlags() & gfxTextRunFactory::TEXT_IS_RTL) != 0;
}

View File

@ -161,7 +161,11 @@ CPUInfo::SetSSEVersion()
);
# else
// On x86, preserve ebx. The compiler needs it for PIC mode.
// Some older processors don't fill the ecx register with cpuid, so clobber
// it before calling cpuid, so that there's no risk of picking random bits
// indicating SSE3/SSE4 are present.
asm (
"xor %%ecx, %%ecx;"
"movl $0x1, %%eax;"
"pushl %%ebx;"
"cpuid;"

View File

@ -1453,9 +1453,12 @@ RestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
// Until we get rid of these phases in bug 960465, we need to skip
// animation restyles during the non-animation phase, and post
// animation restyles so that we restyle those elements again in the
// animation phase.
// animation phase. Furthermore, we need to add
// eRestyle_ChangeAnimationPhaseDescendants so that we actually honor
// these booleans in all cases.
mSkipAnimationRules = true;
mPostAnimationRestyles = true;
aRestyleHint |= eRestyle_ChangeAnimationPhaseDescendants;
DoRebuildAllStyleData(mPendingRestyles, aExtraHint, aRestyleHint);
@ -1495,8 +1498,12 @@ RestyleManager::DoRebuildAllStyleData(RestyleTracker& aRestyleTracker,
// different styles). If we use up the hint for one of the
// ancestors that we hit first, then we'll fail to do the restyling
// we need to do.
aRestyleTracker.AddPendingRestyle(mPresContext->Document()->GetRootElement(),
aRestyleHint, nsChangeHint(0));
Element* root = mPresContext->Document()->GetRootElement();
if (root) {
// If the root element is gone, dropping the hint on the floor
// should be fine.
aRestyleTracker.AddPendingRestyle(root, aRestyleHint, nsChangeHint(0));
}
aRestyleHint = nsRestyleHint(0);
}
@ -2515,11 +2522,13 @@ ElementRestyler::Restyle(nsRestyleHint aRestyleHint)
}
}
// If we are restyling this frame with eRestyle_Self, we restyle
// children with nsRestyleHint(0). But we pass the eRestyle_ForceDescendants
// flag down too.
// If we are restyling this frame with eRestyle_Self or weaker hints,
// we restyle children with nsRestyleHint(0). But we pass the
// eRestyle_ChangeAnimationPhaseDescendants and eRestyle_ForceDescendants
// flags down too.
nsRestyleHint childRestyleHint =
nsRestyleHint(aRestyleHint & (eRestyle_Subtree |
eRestyle_ChangeAnimationPhaseDescendants |
eRestyle_ForceDescendants));
nsRefPtr<nsStyleContext> oldContext = mFrame->StyleContext();
@ -3736,7 +3745,9 @@ RestyleManager::RestyleHintToString(nsRestyleHint aHint)
bool any = false;
const char* names[] = { "Self", "Subtree", "LaterSiblings", "CSSTransitions",
"CSSAnimations", "SVGAttrAnimations", "StyleAttribute",
"ChangeAnimationPhase", "Force", "ForceDescendants" };
"ChangeAnimationPhase",
"ChangeAnimationPhaseDescendants",
"Force", "ForceDescendants" };
uint32_t hint = aHint & ((1 << ArrayLength(names)) - 1);
uint32_t rest = aHint & ~((1 << ArrayLength(names)) - 1);
for (uint32_t i = 0; i < ArrayLength(names); i++) {

View File

@ -4162,8 +4162,12 @@ nsCSSRendering::PaintDecorationLine(nsIFrame* aFrame,
return;
}
// The y position should be set to the middle of the line.
rect.y += lineThickness / 2;
// The block-direction position should be set to the middle of the line.
if (aVertical) {
rect.x -= lineThickness / 2;
} else {
rect.y += lineThickness / 2;
}
switch (aStyle) {
case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
@ -4360,12 +4364,18 @@ nsCSSRendering::DecorationLineToPath(nsIFrame* aFrame,
gfxFloat lineThickness = std::max(NS_round(aLineSize.height), 1.0);
// The y position should be set to the middle of the line.
rect.y += lineThickness / 2;
aGfxContext->Rectangle
(gfxRect(gfxPoint(rect.TopLeft() - gfxPoint(0.0, lineThickness / 2)),
gfxSize(rect.Width(), lineThickness)));
// The block-direction position should be set to the middle of the line.
if (aVertical) {
rect.x -= lineThickness / 2;
aGfxContext->Rectangle
(gfxRect(gfxPoint(rect.TopLeft() - gfxPoint(lineThickness / 2, 0.0)),
gfxSize(lineThickness, rect.Height())));
} else {
rect.y += lineThickness / 2;
aGfxContext->Rectangle
(gfxRect(gfxPoint(rect.TopLeft() - gfxPoint(0.0, lineThickness / 2)),
gfxSize(rect.Width(), lineThickness)));
}
}
nsRect

View File

@ -309,9 +309,14 @@ nsCaret::GetGeometryForFrame(nsIFrame* aFrame,
descent = fm->MaxDescent();
}
nscoord height = ascent + descent;
bool vertical = aFrame->GetWritingMode().IsVertical();
WritingMode wm = aFrame->GetWritingMode();
bool vertical = wm.IsVertical();
if (vertical) {
framePos.x = baseline - ascent;
if (wm.IsLineInverted()) {
framePos.x = baseline - descent;
} else {
framePos.x = baseline - ascent;
}
} else {
framePos.y = baseline - ascent;
}

View File

@ -346,16 +346,20 @@ enum nsRestyleHint {
// FIXME: Remove this as part of bug 960465.
eRestyle_ChangeAnimationPhase = (1 << 7),
// Same as the previous, except this applies to the entire subtree.
// FIXME: Remove this as part of bug 960465.
eRestyle_ChangeAnimationPhaseDescendants = (1 << 8),
// Continue the restyling process to the current frame's children even
// if this frame's restyling resulted in no style changes.
eRestyle_Force = (1<<8),
eRestyle_Force = (1<<9),
// Continue the restyling process to all of the current frame's
// descendants, even if any frame's restyling resulted in no style
// changes. (Implies eRestyle_Force.) Note that this is weaker than
// eRestyle_Subtree, which makes us rerun selector matching on all
// descendants rather than just continuing the restyling process.
eRestyle_ForceDescendants = (1<<9),
eRestyle_ForceDescendants = (1<<10),
};
// The functions below need an integral type to cast to to avoid

View File

@ -3468,10 +3468,11 @@ nsLayoutUtils::GetFontMetricsForStyleContext(nsStyleContext* aStyleContext,
if (aInflation != 1.0f) {
font.size = NSToCoordRound(font.size * aInflation);
}
WritingMode wm(aStyleContext->StyleVisibility());
WritingMode wm(aStyleContext);
return pc->DeviceContext()->GetMetricsFor(
font, aStyleContext->StyleFont()->mLanguage,
wm.IsVertical() ? gfxFont::eVertical : gfxFont::eHorizontal,
wm.IsVertical() && !wm.IsSideways()
? gfxFont::eVertical : gfxFont::eHorizontal,
fs, tp, *aFontMetrics);
}
@ -4868,9 +4869,11 @@ nsLayoutUtils::PaintTextShadow(const nsIFrame* aFrame,
/* static */ nscoord
nsLayoutUtils::GetCenteredFontBaseline(nsFontMetrics* aFontMetrics,
nscoord aLineHeight)
nscoord aLineHeight,
bool aIsInverted)
{
nscoord fontAscent = aFontMetrics->MaxAscent();
nscoord fontAscent = aIsInverted ? aFontMetrics->MaxDescent()
: aFontMetrics->MaxAscent();
nscoord fontHeight = aFontMetrics->MaxHeight();
nscoord leading = aLineHeight - fontHeight;
@ -5846,7 +5849,7 @@ nsLayoutUtils::GetTextRunFlagsForStyle(nsStyleContext* aStyleContext,
default:
break;
}
WritingMode wm(aStyleContext->StyleVisibility());
WritingMode wm(aStyleContext);
if (wm.IsVertical()) {
switch (aStyleText->mTextOrientation) {
case NS_STYLE_TEXT_ORIENTATION_MIXED:
@ -7323,7 +7326,8 @@ nsLayoutUtils::SetBSizeFromFontMetrics(const nsIFrame* aFrame,
// The height of our box is the sum of our font size plus the top
// and bottom border and padding. The height of children do not
// affect our height.
aMetrics.SetBlockStartAscent(fm->MaxAscent());
aMetrics.SetBlockStartAscent(aLineWM.IsLineInverted() ? fm->MaxDescent()
: fm->MaxAscent());
aMetrics.BSize(aLineWM) = fm->MaxHeight();
} else {
NS_WARNING("Cannot get font metrics - defaulting sizes to 0");

View File

@ -1391,11 +1391,15 @@ public:
/**
* Gets the baseline to vertically center text from a font within a
* line of specified height.
* aIsInverted: true if the text is inverted relative to the block
* direction, so that the block-dir "ascent" corresponds to font
* descent. (Applies to sideways text in vertical-lr mode.)
*
* Returns the baseline position relative to the top of the line.
*/
static nscoord GetCenteredFontBaseline(nsFontMetrics* aFontMetrics,
nscoord aLineHeight);
nscoord aLineHeight,
bool aIsInverted);
/**
* Derive a baseline of |aFrame| (measured from its top border edge)

View File

@ -519,7 +519,8 @@ nsTextControlFrame::Reflow(nsPresContext* aPresContext,
inflation);
// now adjust for our borders and padding
aDesiredSize.SetBlockStartAscent(
nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight) +
nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight,
wm.IsLineInverted()) +
aReflowState.ComputedLogicalBorderPadding().BStart(wm));
// overflow handling

View File

@ -7,7 +7,7 @@
#define WritingModes_h_
#include "nsRect.h"
#include "nsStyleStruct.h"
#include "nsStyleContext.h"
// If WRITING_MODE_VERTICAL_ENABLED is defined, we will attempt to support
// the vertical writing-mode values; if it is not defined, then
@ -168,12 +168,17 @@ public:
/**
* Return true if LTR. (Convenience method)
*/
bool IsBidiLTR() const { return eBidiLTR == (mWritingMode & eBidiMask); }
bool IsBidiLTR() const { return eBidiLTR == GetBidiDir(); }
/**
* True if vertical-mode block direction is LR (convenience method).
*/
bool IsVerticalLR() const { return eBlockLR == (mWritingMode & eBlockMask); }
bool IsVerticalLR() const { return eBlockLR == GetBlockDir(); }
/**
* True if vertical-mode block direction is RL (convenience method).
*/
bool IsVerticalRL() const { return eBlockRL == GetBlockDir(); }
/**
* True if vertical writing mode, i.e. when
@ -208,6 +213,20 @@ public:
return IsLineInverted() ? -1 : 1;
}
/**
* True if the text-orientation will force all text to be rendered sideways
* in vertical lines, in which case we should prefer an alphabetic baseline;
* otherwise, the default is centered.
* Note that some glyph runs may be rendered sideways even if this is false,
* due to text-orientation:mixed resolution, but in that case the dominant
* baseline remains centered.
*/
#ifdef WRITING_MODE_VERTICAL_ENABLED
bool IsSideways() const { return !!(mWritingMode & eSidewaysMask); }
#else
bool IsSideways() const { return false; }
#endif
/**
* Default constructor gives us a horizontal, LTR writing mode.
* XXX We will probably eliminate this and require explicit initialization
@ -220,25 +239,49 @@ public:
/**
* Construct writing mode based on a style context
*/
explicit WritingMode(const nsStyleVisibility* aStyleVisibility)
explicit WritingMode(nsStyleContext* aStyleContext)
{
NS_ASSERTION(aStyleVisibility, "we need an nsStyleVisibility here");
NS_ASSERTION(aStyleContext, "we need an nsStyleContext here");
const nsStyleVisibility* styleVisibility = aStyleContext->StyleVisibility();
#ifdef WRITING_MODE_VERTICAL_ENABLED
switch (aStyleVisibility->mWritingMode) {
switch (styleVisibility->mWritingMode) {
case NS_STYLE_WRITING_MODE_HORIZONTAL_TB:
mWritingMode = 0;
break;
case NS_STYLE_WRITING_MODE_VERTICAL_LR:
{
mWritingMode = eBlockFlowMask |
eLineOrientMask | //XXX needs update when text-orientation added
eLineOrientMask |
eOrientationMask;
uint8_t textOrientation = aStyleContext->StyleText()->mTextOrientation;
#if 0 // not yet implemented
if (textOrientation == NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_LEFT) {
mWritingMode &= ~eLineOrientMask;
}
#endif
if (textOrientation >= NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT) {
mWritingMode |= eSidewaysMask;
}
break;
}
case NS_STYLE_WRITING_MODE_VERTICAL_RL:
{
mWritingMode = eOrientationMask;
uint8_t textOrientation = aStyleContext->StyleText()->mTextOrientation;
#if 0 // not yet implemented
if (textOrientation == NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_LEFT) {
mWritingMode |= eLineOrientMask;
}
#endif
if (textOrientation >= NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT) {
mWritingMode |= eSidewaysMask;
}
break;
}
default:
NS_NOTREACHED("unknown writing mode!");
@ -249,7 +292,7 @@ public:
mWritingMode = 0;
#endif
if (NS_STYLE_DIRECTION_RTL == aStyleVisibility->mDirection) {
if (NS_STYLE_DIRECTION_RTL == styleVisibility->mDirection) {
mWritingMode |= eInlineFlowMask | //XXX needs update when text-orientation added
eBidiMask;
}
@ -325,6 +368,10 @@ private:
// Note: We have one excess bit of info; WritingMode can pack into 4 bits.
// But since we have space, we're caching interesting things for fast access.
eSidewaysMask = 0x20, // true means text-orientation is sideways-*,
// which means we'll use alphabetic instead of
// centered default baseline for vertical text
// Masks for output enums
eInlineMask = 0x03,
eBlockMask = 0x05

View File

@ -122,7 +122,8 @@ BRFrame::Reflow(nsPresContext* aPresContext,
if (fm) {
nscoord logicalHeight = aReflowState.CalcLineHeight();
finalSize.BSize(wm) = logicalHeight;
aMetrics.SetBlockStartAscent(nsLayoutUtils::GetCenteredFontBaseline(fm, logicalHeight));
aMetrics.SetBlockStartAscent(nsLayoutUtils::GetCenteredFontBaseline(
fm, logicalHeight, wm.IsLineInverted()));
}
else {
aMetrics.SetBlockStartAscent(aMetrics.BSize(wm) = 0);

View File

@ -515,7 +515,9 @@ nsBlockFrame::GetCaretBaseline() const
nscoord lineHeight =
nsHTMLReflowState::CalcLineHeight(GetContent(), StyleContext(),
contentRect.height, inflation);
return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight) + bp.top;
const WritingMode wm = GetWritingMode();
return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight,
wm.IsLineInverted()) + bp.top;
}
/////////////////////////////////////////////////////////////////////////////
@ -1231,6 +1233,42 @@ nsBlockFrame::Reflow(nsPresContext* aPresContext,
// Compute our final size
nscoord blockEndEdgeOfChildren;
ComputeFinalSize(*reflowState, state, aMetrics, &blockEndEdgeOfChildren);
// If the block direction is right-to-left, we need to update the bounds of
// lines that were placed relative to mContainerWidth during reflow, as
// we typically do not know the true container width (block-dir size of the
// finished paragraph/block) until we've reflowed all its children. So we
// use a "fake" mContainerWidth during reflow (see nsBlockReflowState's
// constructor) and then fix up the positions of the lines here, once the
// final block size is known.
//
// Note that writing-mode:vertical-rl is the only case where the block
// logical direction progresses in a negative physical direction, and
// therefore block-dir coordinate conversion depends on knowing the width
// of the coordinate space in order to translate between the logical and
// physical origins.
if (wm.GetBlockDir() == WritingMode::BlockDir::eBlockRL) {
nscoord deltaX = aMetrics.Width() - state.mContainerWidth;
if (deltaX) {
for (line_iterator line = begin_lines(), end = end_lines();
line != end; line++) {
SlideLine(state, line, -deltaX);
}
for (nsIFrame* f = mFloats.FirstChild(); f; f = f->GetNextSibling()) {
nsPoint physicalDelta(deltaX, 0);
f->MovePositionBy(physicalDelta);
}
nsFrameList* bulletList = GetOutsideBulletList();
if (bulletList) {
nsPoint physicalDelta(deltaX, 0);
for (nsIFrame* f = bulletList->FirstChild(); f;
f = f->GetNextSibling()) {
f->MovePositionBy(physicalDelta);
}
}
}
}
nsRect areaBounds = nsRect(0, 0, aMetrics.Width(), aMetrics.Height());
ComputeOverflowAreas(areaBounds, reflowState->mStyleDisplay,
blockEndEdgeOfChildren, aMetrics.mOverflowAreas);
@ -2516,7 +2554,8 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState)
nsLayoutUtils::FontSizeInflationFor(this));
nscoord minAscent =
nsLayoutUtils::GetCenteredFontBaseline(fm, aState.mMinLineHeight);
nsLayoutUtils::GetCenteredFontBaseline(fm, aState.mMinLineHeight,
wm.IsLineInverted());
nscoord minDescent = aState.mMinLineHeight - minAscent;
aState.mBCoord += std::max(minAscent, metrics.BlockStartAscent()) +
@ -2731,12 +2770,12 @@ nsBlockFrame::PullFrameFrom(nsLineBox* aLine,
void
nsBlockFrame::SlideLine(nsBlockReflowState& aState,
nsLineBox* aLine, nscoord aDY)
nsLineBox* aLine, nscoord aDeltaBCoord)
{
NS_PRECONDITION(aDY != 0, "why slide a line nowhere?");
NS_PRECONDITION(aDeltaBCoord != 0, "why slide a line nowhere?");
// Adjust line state
aLine->SlideBy(aDY, aState.mContainerWidth);
aLine->SlideBy(aDeltaBCoord, aState.mContainerWidth);
// Adjust the frames in the line
nsIFrame* kid = aLine->mFirstChild;
@ -2744,23 +2783,26 @@ nsBlockFrame::SlideLine(nsBlockReflowState& aState,
return;
}
WritingMode wm = GetWritingMode();
LogicalPoint translation(wm, 0, aDeltaBCoord);
if (aLine->IsBlock()) {
if (aDY) {
kid->MovePositionBy(nsPoint(0, aDY));
if (aDeltaBCoord) {
kid->MovePositionBy(wm, translation);
}
// Make sure the frame's view and any child views are updated
nsContainerFrame::PlaceFrameView(kid);
}
else {
// Adjust the Y coordinate of the frames in the line.
// Note: we need to re-position views even if aDY is 0, because
// Adjust the block-dir coordinate of the frames in the line.
// Note: we need to re-position views even if aDeltaBCoord is 0, because
// one of our parent frames may have moved and so the view's position
// relative to its parent may have changed
// relative to its parent may have changed.
int32_t n = aLine->GetChildCount();
while (--n >= 0) {
if (aDY) {
kid->MovePositionBy(nsPoint(0, aDY));
if (aDeltaBCoord) {
kid->MovePositionBy(wm, translation);
}
// Make sure the frame's view and any child views are updated
nsContainerFrame::PlaceFrameView(kid);

View File

@ -408,11 +408,11 @@ protected:
mState |= aFlags;
}
/** move the frames contained by aLine by aDY
/** move the frames contained by aLine by aDeltaBCoord
* if aLine is a block, its child floats are added to the state manager
*/
void SlideLine(nsBlockReflowState& aState,
nsLineBox* aLine, nscoord aDY);
nsLineBox* aLine, nscoord aDeltaBCoord);
void ComputeFinalSize(const nsHTMLReflowState& aReflowState,
nsBlockReflowState& aState,

View File

@ -54,8 +54,19 @@ nsBlockReflowState::nsBlockReflowState(const nsHTMLReflowState& aReflowState,
aFrame->GetLogicalSkipSides(&aReflowState);
mBorderPadding.ApplySkipSides(logicalSkipSides);
// Note that mContainerWidth is the physical width!
mContainerWidth = aReflowState.ComputedWidth() + mBorderPadding.LeftRight(wm);
// Note that mContainerWidth is the physical width, needed to convert
// logical block-coordinates in vertical-rl writing mode (measured from a
// RHS origin) to physical coordinates within the containing block.
// If aReflowState doesn't have a constrained ComputedWidth(), we set it to
// zero, which means lines will be positioned (physically) incorrectly;
// we will fix them up at the end of nsBlockFrame::Reflow, after we know
// the total block-size of the frame.
mContainerWidth = aReflowState.ComputedWidth();
if (mContainerWidth == NS_UNCONSTRAINEDSIZE) {
mContainerWidth = 0;
}
mContainerWidth += mBorderPadding.LeftRight(wm);
if ((aBStartMarginRoot && !logicalSkipSides.BStart()) ||
0 != mBorderPadding.BStart(wm)) {

View File

@ -203,6 +203,8 @@ public:
mozilla::WritingMode wm = mReflowState.GetWritingMode();
return mContentArea.Size(wm).ConvertTo(aWM, wm);
}
// Physical width. Use only for physical <-> logical coordinate conversion.
nscoord mContainerWidth;
// Continuation out-of-flow float frames that need to move to our

View File

@ -1339,7 +1339,12 @@ nsFrame::GetLogicalBaseline(WritingMode aWritingMode) const
{
NS_ASSERTION(!NS_SUBTREE_DIRTY(this),
"frame must not be dirty");
// Default to the bottom margin edge, per CSS2.1's definition of the
// Baseline for inverted line content is the top (block-start) margin edge,
// as the frame is in effect "flipped" for alignment purposes.
if (aWritingMode.IsLineInverted()) {
return -GetLogicalUsedMargin(aWritingMode).BStart(aWritingMode);
}
// Otherwise, the bottom margin edge, per CSS2.1's definition of the
// 'baseline' value of 'vertical-align'.
return BSize(aWritingMode) +
GetLogicalUsedMargin(aWritingMode).BEnd(aWritingMode);

View File

@ -608,7 +608,7 @@ public:
* The frame's writing-mode, used for logical layout computations.
*/
mozilla::WritingMode GetWritingMode() const {
return mozilla::WritingMode(StyleVisibility());
return mozilla::WritingMode(StyleContext());
}
/**
@ -744,6 +744,15 @@ public:
*/
void MovePositionBy(const nsPoint& aTranslation);
/**
* As above, using a logical-point delta in a given writing mode.
*/
void MovePositionBy(mozilla::WritingMode aWritingMode,
const mozilla::LogicalPoint& aTranslation)
{
MovePositionBy(aTranslation.GetPhysicalPoint(aWritingMode, 0));
}
/**
* Return frame's position without relative positioning
*/

View File

@ -473,8 +473,10 @@ public:
mContainerWidth = aContainerWidth;
mBounds.BStart(mWritingMode) += aDBCoord;
if (mData) {
nsPoint physicalDelta = mozilla::LogicalPoint(mWritingMode, 0, aDBCoord).
GetPhysicalPoint(mWritingMode, 0);
NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
mData->mOverflowAreas.Overflow(otype).y += aDBCoord;
mData->mOverflowAreas.Overflow(otype) += physicalDelta;
}
}
}
@ -585,9 +587,13 @@ public:
nsIFrame* mFirstChild;
mozilla::WritingMode mWritingMode;
// Physical width. Use only for physical <-> logical coordinate conversion.
nscoord mContainerWidth;
private:
mozilla::LogicalRect mBounds;
public:
const mozilla::LogicalRect& GetBounds() { return mBounds; }
nsRect GetPhysicalBounds() const

View File

@ -608,8 +608,8 @@ nsLineLayout::NewPerFrameData(nsIFrame* aFrame)
WritingMode frameWM = aFrame->GetWritingMode();
WritingMode lineWM = mRootSpan->mWritingMode;
pfd->mBounds = LogicalRect(lineWM);
pfd->mMargin = LogicalMargin(frameWM);
pfd->mBorderPadding = LogicalMargin(frameWM);
pfd->mMargin = LogicalMargin(lineWM);
pfd->mBorderPadding = LogicalMargin(lineWM);
pfd->mOffsets = LogicalMargin(frameWM);
pfd->mJustificationInfo = JustificationInfo();
@ -809,9 +809,9 @@ nsLineLayout::ReflowFrame(nsIFrame* aFrame,
}
WritingMode stateWM = reflowState.GetWritingMode();
pfd->mMargin =
reflowState.ComputedLogicalMargin().ConvertTo(frameWM, stateWM);
reflowState.ComputedLogicalMargin().ConvertTo(lineWM, stateWM);
pfd->mBorderPadding =
reflowState.ComputedLogicalBorderPadding().ConvertTo(frameWM, stateWM);
reflowState.ComputedLogicalBorderPadding().ConvertTo(lineWM, stateWM);
pfd->SetFlag(PFD_RELATIVEPOS,
reflowState.mStyleDisplay->IsRelativelyPositionedStyle());
if (pfd->GetFlag(PFD_RELATIVEPOS)) {
@ -1086,7 +1086,7 @@ nsLineLayout::AllowForStartMargin(PerFrameData* pfd,
"How'd we get a floated inline frame? "
"The frame ctor should've dealt with this.");
WritingMode frameWM = pfd->mFrame->GetWritingMode();
WritingMode lineWM = mRootSpan->mWritingMode;
// Only apply start-margin on the first-in flow for inline frames,
// and make sure to not apply it to any inline other than the first
@ -1101,7 +1101,7 @@ nsLineLayout::AllowForStartMargin(PerFrameData* pfd,
NS_STYLE_BOX_DECORATION_BREAK_SLICE) {
// Zero this out so that when we compute the max-element-width of
// the frame we will properly avoid adding in the starting margin.
pfd->mMargin.IStart(frameWM) = 0;
pfd->mMargin.IStart(lineWM) = 0;
} else {
NS_WARN_IF_FALSE(NS_UNCONSTRAINEDSIZE != aReflowState.AvailableISize(),
"have unconstrained inline-size; this should only result "
@ -1112,7 +1112,9 @@ nsLineLayout::AllowForStartMargin(PerFrameData* pfd,
// in the reflow state), adjust available inline-size to account for the
// start margin. The end margin will be accounted for when we
// finish flowing the frame.
aReflowState.AvailableISize() -= pfd->mMargin.IStart(frameWM);
WritingMode wm = aReflowState.GetWritingMode();
aReflowState.AvailableISize() -=
pfd->mMargin.ConvertTo(wm, lineWM).IStart(wm);
}
}
}
@ -1151,7 +1153,6 @@ nsLineLayout::CanPlaceFrame(PerFrameData* pfd,
*aOptionalBreakAfterFits = true;
WritingMode frameWM = pfd->mFrame->GetWritingMode();
WritingMode lineWM = mRootSpan->mWritingMode;
/*
* We want to only apply the end margin if we're the last continuation and
@ -1176,14 +1177,12 @@ nsLineLayout::CanPlaceFrame(PerFrameData* pfd,
!pfd->GetFlag(PFD_ISLETTERFRAME) &&
pfd->mFrame->StyleBorder()->mBoxDecorationBreak ==
NS_STYLE_BOX_DECORATION_BREAK_SLICE) {
pfd->mMargin.IEnd(frameWM) = 0;
pfd->mMargin.IEnd(lineWM) = 0;
}
// Convert the frame's margins to the line's writing mode and apply
// the start margin to the frame bounds.
LogicalMargin usedMargins = pfd->mMargin.ConvertTo(lineWM, frameWM);
nscoord startMargin = usedMargins.IStart(lineWM);
nscoord endMargin = usedMargins.IEnd(lineWM);
// Apply the start margin to the frame bounds.
nscoord startMargin = pfd->mMargin.IStart(lineWM);
nscoord endMargin = pfd->mMargin.IEnd(lineWM);
pfd->mBounds.IStart(lineWM) += startMargin;
@ -1310,7 +1309,6 @@ nsLineLayout::CanPlaceFrame(PerFrameData* pfd,
void
nsLineLayout::PlaceFrame(PerFrameData* pfd, nsHTMLReflowMetrics& aMetrics)
{
WritingMode frameWM = pfd->mFrame->GetWritingMode();
WritingMode lineWM = mRootSpan->mWritingMode;
// Record ascent and update max-ascent and max-descent values
@ -1322,7 +1320,7 @@ nsLineLayout::PlaceFrame(PerFrameData* pfd, nsHTMLReflowMetrics& aMetrics)
// Advance to next inline coordinate
mCurrentSpan->mICoord = pfd->mBounds.IEnd(lineWM) +
pfd->mMargin.ConvertTo(lineWM, frameWM).IEnd(lineWM);
pfd->mMargin.IEnd(lineWM);
// Count the number of non-placeholder frames on the line...
if (pfd->mFrame->GetType() == nsGkAtoms::placeholderFrame) {
@ -1509,7 +1507,6 @@ nsLineLayout::PlaceTopBottomFrames(PerSpanData* psd,
#ifdef DEBUG
NS_ASSERTION(0xFF != pfd->mBlockDirAlign, "umr");
#endif
WritingMode frameWM = pfd->mFrame->GetWritingMode();
WritingMode lineWM = mRootSpan->mWritingMode;
nscoord containerWidth = ContainerWidthForSpan(psd);
switch (pfd->mBlockDirAlign) {
@ -1519,7 +1516,7 @@ nsLineLayout::PlaceTopBottomFrames(PerSpanData* psd,
}
else {
pfd->mBounds.BStart(lineWM) =
-aDistanceFromStart + pfd->mMargin.BStart(frameWM);
-aDistanceFromStart + pfd->mMargin.BStart(lineWM);
}
pfd->mFrame->SetRect(lineWM, pfd->mBounds, containerWidth);
#ifdef NOISY_BLOCKDIR_ALIGN
@ -1527,7 +1524,7 @@ nsLineLayout::PlaceTopBottomFrames(PerSpanData* psd,
nsFrame::ListTag(stdout, pfd->mFrame);
printf(": y=%d dTop=%d [bp.top=%d topLeading=%d]\n",
pfd->mBounds.BStart(lineWM), aDistanceFromStart,
span ? pfd->mBorderPadding.BStart(frameWM) : 0,
span ? pfd->mBorderPadding.BStart(lineWM) : 0,
span ? span->mBStartLeading : 0);
#endif
break;
@ -1539,7 +1536,7 @@ nsLineLayout::PlaceTopBottomFrames(PerSpanData* psd,
}
else {
pfd->mBounds.BStart(lineWM) = -aDistanceFromStart + aLineBSize -
pfd->mMargin.BEnd(frameWM) - pfd->mBounds.BSize(lineWM);
pfd->mMargin.BEnd(lineWM) - pfd->mBounds.BSize(lineWM);
}
pfd->mFrame->SetRect(lineWM, pfd->mBounds, containerWidth);
#ifdef NOISY_BLOCKDIR_ALIGN
@ -1598,7 +1595,6 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
// - it has a prev-in-flow
// - it has no next in flow
// - it's zero sized
WritingMode frameWM = spanFramePFD->mFrame->GetWritingMode();
WritingMode lineWM = mRootSpan->mWritingMode;
bool emptyContinuation = psd != mRootSpan &&
spanFrame->GetPrevInFlow() && !spanFrame->GetNextInFlow() &&
@ -1616,14 +1612,14 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
if (psd != mRootSpan) {
WritingMode frameWM = spanFramePFD->mFrame->GetWritingMode();
printf(" bp=%d,%d,%d,%d margin=%d,%d,%d,%d",
spanFramePFD->mBorderPadding.Top(frameWM),
spanFramePFD->mBorderPadding.Right(frameWM),
spanFramePFD->mBorderPadding.Bottom(frameWM),
spanFramePFD->mBorderPadding.Left(frameWM),
spanFramePFD->mMargin.Top(frameWM),
spanFramePFD->mMargin.Right(frameWM),
spanFramePFD->mMargin.Bottom(frameWM),
spanFramePFD->mMargin.Left(frameWM));
spanFramePFD->mBorderPadding.Top(lineWM),
spanFramePFD->mBorderPadding.Right(lineWM),
spanFramePFD->mBorderPadding.Bottom(lineWM),
spanFramePFD->mBorderPadding.Left(lineWM),
spanFramePFD->mMargin.Top(lineWM),
spanFramePFD->mMargin.Right(lineWM),
spanFramePFD->mMargin.Bottom(lineWM),
spanFramePFD->mMargin.Left(lineWM));
}
printf("\n");
#endif
@ -1725,8 +1721,7 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
mBlockReflowState->ComputedHeight(),
inflation);
nscoord contentBSize = spanFramePFD->mBounds.BSize(lineWM) -
spanFramePFD->mBorderPadding.BStart(frameWM) -
spanFramePFD->mBorderPadding.BEnd(frameWM);
spanFramePFD->mBorderPadding.BStartEnd(lineWM);
// Special-case for a ::first-letter frame, set the line height to
// the frame block size if the user has left line-height == normal
@ -1759,7 +1754,7 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
// If there are child frames in this span that stick out of this area
// then the minBCoord and maxBCoord are updated by the amount of logical
// blockSize that is outside this range.
minBCoord = spanFramePFD->mBorderPadding.BStart(frameWM) -
minBCoord = spanFramePFD->mBorderPadding.BStart(lineWM) -
psd->mBStartLeading;
maxBCoord = minBCoord + psd->mLogicalBSize;
}
@ -1776,8 +1771,8 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
printf(": baseLine=%d logicalBSize=%d topLeading=%d h=%d bp=%d,%d zeroEffectiveSpanBox=%s\n",
baselineBCoord, psd->mLogicalBSize, psd->mBStartLeading,
spanFramePFD->mBounds.BSize(lineWM),
spanFramePFD->mBorderPadding.Top(frameWM),
spanFramePFD->mBorderPadding.Bottom(frameWM),
spanFramePFD->mBorderPadding.Top(lineWM),
spanFramePFD->mBorderPadding.Bottom(lineWM),
zeroEffectiveSpanBox ? "yes" : "no");
#endif
}
@ -1787,7 +1782,6 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
PerFrameData* pfd = psd->mFirstFrame;
while (nullptr != pfd) {
nsIFrame* frame = pfd->mFrame;
WritingMode frameWM = frame->GetWritingMode();
// sanity check (see bug 105168, non-reproducible crashes from null frame)
NS_ASSERTION(frame, "null frame in PerFrameData - something is very very bad");
@ -1807,7 +1801,7 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
// For other elements the logical block size is the same as the
// frame's block size plus its margins.
logicalBSize = pfd->mBounds.BSize(lineWM) +
pfd->mMargin.BStartEnd(frameWM);
pfd->mMargin.BStartEnd(lineWM);
if (logicalBSize < 0 &&
mPresContext->CompatibilityMode() == eCompatibility_NavQuirks) {
pfd->mAscent -= logicalBSize;
@ -1915,7 +1909,7 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
else {
pfd->mBounds.BStart(lineWM) = baselineBCoord -
(parentXHeight + logicalBSize)/2 +
pfd->mMargin.BStart(frameWM);
pfd->mMargin.BStart(lineWM);
}
pfd->mBlockDirAlign = VALIGN_OTHER;
break;
@ -1930,11 +1924,11 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
nscoord parentAscent = fm->MaxAscent();
if (frameSpan) {
pfd->mBounds.BStart(lineWM) = baselineBCoord - parentAscent -
pfd->mBorderPadding.BStart(frameWM) + frameSpan->mBStartLeading;
pfd->mBorderPadding.BStart(lineWM) + frameSpan->mBStartLeading;
}
else {
pfd->mBounds.BStart(lineWM) = baselineBCoord - parentAscent +
pfd->mMargin.BStart(frameWM);
pfd->mMargin.BStart(lineWM);
}
pfd->mBlockDirAlign = VALIGN_OTHER;
break;
@ -1948,13 +1942,13 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
if (frameSpan) {
pfd->mBounds.BStart(lineWM) = baselineBCoord + parentDescent -
pfd->mBounds.BSize(lineWM) +
pfd->mBorderPadding.BEnd(frameWM) -
pfd->mBorderPadding.BEnd(lineWM) -
frameSpan->mBEndLeading;
}
else {
pfd->mBounds.BStart(lineWM) = baselineBCoord + parentDescent -
pfd->mBounds.BSize(lineWM) -
pfd->mMargin.BEnd(frameWM);
pfd->mMargin.BEnd(lineWM);
}
pfd->mBlockDirAlign = VALIGN_OTHER;
break;
@ -1969,7 +1963,7 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
}
else {
pfd->mBounds.BStart(lineWM) = baselineBCoord - logicalBSize/2 +
pfd->mMargin.BStart(frameWM);
pfd->mMargin.BStart(lineWM);
}
pfd->mBlockDirAlign = VALIGN_OTHER;
break;
@ -2035,7 +2029,7 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
}
else {
blockStart = pfd->mBounds.BStart(lineWM) -
pfd->mMargin.BStart(frameWM);
pfd->mMargin.BStart(lineWM);
blockEnd = blockStart + logicalBSize;
}
if (!preMode &&
@ -2054,8 +2048,8 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
#ifdef NOISY_BLOCKDIR_ALIGN
printf(" [frame]raw: a=%d h=%d bp=%d,%d logical: h=%d leading=%d y=%d minBCoord=%d maxBCoord=%d\n",
pfd->mAscent, pfd->mBounds.BSize(lineWM),
pfd->mBorderPadding.Top(frameWM),
pfd->mBorderPadding.Bottom(frameWM),
pfd->mBorderPadding.Top(lineWM),
pfd->mBorderPadding.Bottom(lineWM),
logicalBSize,
frameSpan ? frameSpan->mBStartLeading : 0,
pfd->mBounds.BStart(lineWM), minBCoord, maxBCoord);
@ -2104,7 +2098,8 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
#endif
nscoord minimumLineBSize = mMinLineBSize;
nscoord blockStart =
-nsLayoutUtils::GetCenteredFontBaseline(fm, minimumLineBSize);
-nsLayoutUtils::GetCenteredFontBaseline(fm, minimumLineBSize,
lineWM.IsLineInverted());
nscoord blockEnd = blockStart + minimumLineBSize;
if (blockStart < minBCoord) minBCoord = blockStart;
@ -2146,8 +2141,8 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
spanFramePFD->mAscent,
psd->mLogicalBSize, psd->mBStartLeading, psd->mBEndLeading);
#endif
nscoord goodMinBCoord = spanFramePFD->mBorderPadding.BStart(frameWM) -
psd->mBStartLeading;
nscoord goodMinBCoord =
spanFramePFD->mBorderPadding.BStart(lineWM) - psd->mBStartLeading;
nscoord goodMaxBCoord = goodMinBCoord + psd->mLogicalBSize;
// For cases like the one in bug 714519 (text-decoration placement

View File

@ -385,9 +385,9 @@ protected:
nsOverflowAreas mOverflowAreas;
// From reflow-state
mozilla::LogicalMargin mMargin;
mozilla::LogicalMargin mBorderPadding;
mozilla::LogicalMargin mOffsets;
mozilla::LogicalMargin mMargin; // in *line* writing mode
mozilla::LogicalMargin mBorderPadding; // in *line* writing mode
mozilla::LogicalMargin mOffsets; // in *frame* writing mode
// state for text justification
mozilla::JustificationInfo mJustificationInfo;
@ -537,6 +537,7 @@ protected:
// frame, if any
nscoord mTrimmableISize;
// Physical width. Use only for physical <-> logical coordinate conversion.
nscoord mContainerWidth;
bool mFirstLetterStyleOK : 1;

View File

@ -1790,13 +1790,13 @@ GetHyphenTextRun(gfxTextRun* aTextRun, gfxContext* aContext, nsTextFrame* aTextF
}
static gfxFont::Metrics
GetFirstFontMetrics(gfxFontGroup* aFontGroup, bool aVertical)
GetFirstFontMetrics(gfxFontGroup* aFontGroup, bool aVerticalMetrics)
{
if (!aFontGroup)
return gfxFont::Metrics();
gfxFont* font = aFontGroup->GetFirstValidFont();
return font->GetMetrics(aVertical ? gfxFont::eVertical :
gfxFont::eHorizontal);
return font->GetMetrics(aVerticalMetrics ? gfxFont::eVertical
: gfxFont::eHorizontal);
}
PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NORMAL == 0);
@ -3161,7 +3161,7 @@ ComputeTabWidthAppUnits(nsIFrame* aFrame, gfxTextRun* aTextRun)
// textruns do
gfxFloat spaceWidthAppUnits =
NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
aTextRun->IsVertical()).spaceWidth *
aTextRun->UseCenterBaseline()).spaceWidth *
aTextRun->GetAppUnitsPerDevUnit());
return textStyle->mTabSize * spaceWidthAppUnits;
}
@ -4768,15 +4768,16 @@ nsTextFrame::GetTextDecorations(
bool useOverride = false;
nscolor overrideColor = NS_RGBA(0, 0, 0, 0);
// frameTopOffset represents the offset to f's top from our baseline in our
// frameBStartOffset represents the offset to f's BStart from our baseline in our
// coordinate space
// baselineOffset represents the offset from our baseline to f's baseline or
// the nearest block's baseline, in our coordinate space, whichever is closest
// during the particular iteration
nscoord frameTopOffset = mAscent,
nscoord frameBStartOffset = mAscent,
baselineOffset = 0;
bool nearestBlockFound = false;
bool vertical = GetWritingMode().IsVertical();
for (nsIFrame* f = this, *fChild = nullptr;
f;
@ -4819,18 +4820,20 @@ nsTextFrame::GetTextDecorations(
const nscoord lineBaselineOffset = LazyGetLineBaselineOffset(fChild,
fBlock);
baselineOffset =
frameTopOffset - fChild->GetNormalPosition().y - lineBaselineOffset;
baselineOffset = frameBStartOffset - lineBaselineOffset -
(vertical ? fChild->GetNormalPosition().x
: fChild->GetNormalPosition().y);
}
}
else if (!nearestBlockFound) {
// use a dummy WritingMode, because nsTextFrame::GetLogicalBaseLine
// doesn't use it anyway
baselineOffset = frameTopOffset - f->GetLogicalBaseline(WritingMode());
baselineOffset = frameBStartOffset - f->GetLogicalBaseline(WritingMode());
}
nearestBlockFound = nearestBlockFound || firstBlock;
frameTopOffset += f->GetNormalPosition().y;
frameBStartOffset +=
vertical ? f->GetNormalPosition().x : f->GetNormalPosition().y;
const uint8_t style = styleText->GetDecorationStyle();
if (textDecorations) {
@ -4915,7 +4918,9 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
nsRect shadowRect =
nsLayoutUtils::GetTextShadowRectsUnion(*aVisualOverflowRect, this);
aVisualOverflowRect->UnionRect(*aVisualOverflowRect, shadowRect);
bool vertical = mTextRun->IsVertical();
bool verticalRun = mTextRun->IsVertical();
bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
bool inverted = GetWritingMode().IsLineInverted();
if (IsFloatingFirstLetterChild()) {
// The underline/overline drawable area must be contained in the overflow
@ -4932,11 +4937,13 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
nsFontMetrics* fontMetrics = aProvider.GetFontMetrics();
nscoord underlineOffset, underlineSize;
fontMetrics->GetUnderline(underlineOffset, underlineSize);
nscoord maxAscent = fontMetrics->MaxAscent();
nscoord maxAscent = inverted ? fontMetrics->MaxDescent()
: fontMetrics->MaxAscent();
gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel();
gfxFloat gfxWidth =
(vertical ? aVisualOverflowRect->height : aVisualOverflowRect->width) /
(verticalRun ? aVisualOverflowRect->height
: aVisualOverflowRect->width) /
appUnitsPerDevUnit;
gfxFloat gfxAscent = gfxFloat(mAscent) / appUnitsPerDevUnit;
gfxFloat gfxMaxAscent = maxAscent / appUnitsPerDevUnit;
@ -4945,11 +4952,11 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
nsRect underlineRect =
nsCSSRendering::GetTextDecorationRect(aPresContext,
gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxUnderlineOffset,
NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle, vertical);
NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle, verticalRun);
nsRect overlineRect =
nsCSSRendering::GetTextDecorationRect(aPresContext,
gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxMaxAscent,
NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle, vertical);
NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle, verticalRun);
aVisualOverflowRect->UnionRect(*aVisualOverflowRect, underlineRect);
aVisualOverflowRect->UnionRect(*aVisualOverflowRect, overlineRect);
@ -4969,11 +4976,16 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
nscoord inflationMinFontSize =
nsLayoutUtils::InflationMinFontSizeFor(aBlock);
const nscoord measure = vertical ? GetSize().height : GetSize().width;
const nscoord measure = verticalRun ? GetSize().height : GetSize().width;
const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(),
gfxWidth = measure / appUnitsPerDevUnit,
ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
nscoord top(nscoord_MAX), bottom(nscoord_MIN);
gfxWidth = measure / appUnitsPerDevUnit;
gfxFloat ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
const WritingMode wm = GetWritingMode();
if (wm.IsVerticalRL()) {
ascent = -ascent;
}
nscoord topOrLeft(nscoord_MAX), bottomOrRight(nscoord_MIN);
// Below we loop through all text decorations and compute the rectangle
// containing all of them, in this frame's coordinate space
for (uint32_t i = 0; i < textDecs.mUnderlines.Length(); ++i) {
@ -4990,18 +5002,23 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
const gfxFont::Metrics metrics =
GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
vertical);
useVerticalMetrics);
const nsRect decorationRect =
nsCSSRendering::GetTextDecorationRect(aPresContext,
gfxSize(gfxWidth, metrics.underlineSize),
ascent, metrics.underlineOffset,
NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle,
vertical) +
verticalRun) +
nsPoint(0, -dec.mBaselineOffset);
top = std::min(decorationRect.y, top);
bottom = std::max(decorationRect.YMost(), bottom);
if (verticalRun) {
topOrLeft = std::min(decorationRect.x, topOrLeft);
bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
} else {
topOrLeft = std::min(decorationRect.y, topOrLeft);
bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
}
}
for (uint32_t i = 0; i < textDecs.mOverlines.Length(); ++i) {
const LineDecoration& dec = textDecs.mOverlines[i];
@ -5017,18 +5034,23 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
const gfxFont::Metrics metrics =
GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
vertical);
useVerticalMetrics);
const nsRect decorationRect =
nsCSSRendering::GetTextDecorationRect(aPresContext,
gfxSize(gfxWidth, metrics.underlineSize),
ascent, metrics.maxAscent,
NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle,
vertical) +
verticalRun) +
nsPoint(0, -dec.mBaselineOffset);
top = std::min(decorationRect.y, top);
bottom = std::max(decorationRect.YMost(), bottom);
if (verticalRun) {
topOrLeft = std::min(decorationRect.x, topOrLeft);
bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
} else {
topOrLeft = std::min(decorationRect.y, topOrLeft);
bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
}
}
for (uint32_t i = 0; i < textDecs.mStrikes.Length(); ++i) {
const LineDecoration& dec = textDecs.mStrikes[i];
@ -5044,23 +5066,29 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
const gfxFont::Metrics metrics =
GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
vertical);
useVerticalMetrics);
const nsRect decorationRect =
nsCSSRendering::GetTextDecorationRect(aPresContext,
gfxSize(gfxWidth, metrics.strikeoutSize),
ascent, metrics.strikeoutOffset,
NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, decorationStyle,
vertical) +
verticalRun) +
nsPoint(0, -dec.mBaselineOffset);
top = std::min(decorationRect.y, top);
bottom = std::max(decorationRect.YMost(), bottom);
if (verticalRun) {
topOrLeft = std::min(decorationRect.x, topOrLeft);
bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
} else {
topOrLeft = std::min(decorationRect.y, topOrLeft);
bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
}
}
aVisualOverflowRect->UnionRect(*aVisualOverflowRect,
vertical ?
nsRect(top, 0, bottom - top, measure) :
nsRect(0, top, measure, bottom - top));
aVisualOverflowRect->UnionRect(
*aVisualOverflowRect,
verticalRun ? nsRect(topOrLeft, 0, bottomOrRight - topOrLeft, measure)
: nsRect(0, topOrLeft, measure, bottomOrRight - topOrLeft));
}
}
// When this frame is not selected, the text-decoration area must be in
@ -5720,19 +5748,20 @@ nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx,
}
gfxFont* firstFont = aProvider.GetFontGroup()->GetFirstValidFont();
bool vertical = mTextRun->IsVertical();
bool verticalRun = mTextRun->IsVertical();
bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
gfxFont::Metrics
decorationMetrics(firstFont->GetMetrics(vertical ?
decorationMetrics(firstFont->GetMetrics(useVerticalMetrics ?
gfxFont::eVertical : gfxFont::eHorizontal));
if (!vertical) {
if (!useVerticalMetrics) {
// The potential adjustment from using gfxFontGroup::GetUnderlineOffset
// is only valid for horizontal text.
// is only valid for horizontal font metrics.
decorationMetrics.underlineOffset =
aProvider.GetFontGroup()->GetUnderlineOffset();
}
gfxFloat startIOffset =
vertical ? aTextBaselinePt.y - aFramePt.y : aTextBaselinePt.x - aFramePt.x;
verticalRun ? aTextBaselinePt.y - aFramePt.y : aTextBaselinePt.x - aFramePt.x;
SelectionIterator iterator(selectedChars, aContentOffset, aContentLength,
aProvider, mTextRun, startIOffset);
gfxFloat iOffset, hyphenWidth;
@ -5740,7 +5769,7 @@ nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx,
int32_t app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
// XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
gfxPoint pt;
if (vertical) {
if (verticalRun) {
pt.x = (aTextBaselinePt.x - mAscent) / app;
} else {
pt.y = (aTextBaselinePt.y - mAscent) / app;
@ -5754,7 +5783,7 @@ nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx,
gfxFloat advance = hyphenWidth +
mTextRun->GetAdvanceWidth(offset, length, &aProvider);
if (type == aSelectionType) {
if (vertical) {
if (verticalRun) {
pt.y = (aFramePt.y + iOffset -
(mTextRun->IsRightToLeft() ? advance : 0)) / app;
} else {
@ -5766,7 +5795,7 @@ nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx,
DrawSelectionDecorations(aCtx, dirtyRect, aSelectionType, this,
aTextPaintStyle, selectedStyle, pt, xInFrame,
width, mAscent / app, decorationMetrics,
aCallbacks, vertical);
aCallbacks, verticalRun);
}
iterator.UpdateWithAdvance(advance);
}
@ -6010,13 +6039,15 @@ nsTextFrame::PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt,
gfxContext* ctx = aRenderingContext->ThebesContext();
const bool rtl = mTextRun->IsRightToLeft();
const bool vertical = mTextRun->IsVertical();
const bool verticalRun = mTextRun->IsVertical();
WritingMode wm = GetWritingMode();
const nscoord frameWidth = GetSize().width;
gfxPoint framePt(aPt.x, aPt.y);
gfxPoint textBaselinePt;
if (vertical) {
textBaselinePt = gfxPoint(aPt.x + mAscent,
rtl ? gfxFloat(aPt.y + GetSize().height) : aPt.y);
if (verticalRun) {
textBaselinePt = // XXX sideways-left will need different handling here
gfxPoint(aPt.x + (wm.IsVerticalLR() ? mAscent : frameWidth - mAscent),
rtl ? aPt.y + GetSize().height : aPt.y);
} else {
textBaselinePt = gfxPoint(rtl ? gfxFloat(aPt.x + frameWidth) : framePt.x,
nsLayoutUtils::GetSnappedBaselineY(this, ctx, aPt.y, mAscent));
@ -6028,7 +6059,7 @@ nsTextFrame::PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt,
&startOffset, &maxLength, &snappedLeftEdge, &snappedRightEdge)) {
return;
}
if (vertical) {
if (verticalRun) {
textBaselinePt.y += rtl ? -snappedRightEdge : snappedLeftEdge;
} else {
textBaselinePt.x += rtl ? -snappedRightEdge : snappedLeftEdge;
@ -6153,17 +6184,43 @@ nsTextFrame::DrawTextRunAndDecorations(
nsTextFrame::DrawPathCallbacks* aCallbacks)
{
const gfxFloat app = aTextStyle.PresContext()->AppUnitsPerDevPixel();
bool vertical = mTextRun->IsVertical();
bool verticalRun = mTextRun->IsVertical();
bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
// XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
nscoord x = NSToCoordRound(aFramePt.x);
nscoord width = vertical ? GetRect().height : GetRect().width;
aClipEdges.Intersect(&x, &width);
nscoord y = NSToCoordRound(aFramePt.y);
gfxPoint decPt(x / app, 0);
gfxSize decSize(width / app, 0);
const gfxFloat ascent = gfxFloat(mAscent) / app;
const gfxFloat frameTop = aFramePt.y;
// 'measure' here is textrun-relative, so for a horizontal run it's the
// width, while for a vertical run it's the height of the decoration
const nsSize frameSize = GetSize();
nscoord measure = verticalRun ? frameSize.height : frameSize.width;
// XXX todo: probably should have a vertical version of this...
if (!verticalRun) {
aClipEdges.Intersect(&x, &measure);
}
// decPt is the physical point where the decoration is to be drawn,
// relative to the frame; one of its coordinates will be updated below.
gfxPoint decPt(x / app, y / app);
gfxFloat& bCoord = verticalRun ? decPt.x : decPt.y;
// decSize is a textrun-relative size, so its 'width' field is actually
// the run-relative measure, and 'height' will be the line thickness
gfxSize decSize(measure / app, 0);
gfxFloat ascent = gfxFloat(mAscent) / app;
// The starting edge of the frame in block direction
gfxFloat frameBStart = verticalRun ? aFramePt.x : aFramePt.y;
// In vertical-rl mode, block coordinates are measured from the right,
// so we need to adjust here.
const WritingMode wm = GetWritingMode();
if (wm.IsVerticalRL()) {
frameBStart += frameSize.width;
ascent = -ascent;
}
gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app,
aDirtyRect.Width() / app, aDirtyRect.Height() / app);
@ -6182,15 +6239,15 @@ nsTextFrame::DrawTextRunAndDecorations(
GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
const gfxFont::Metrics metrics =
GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
vertical);
useVerticalMetrics);
decSize.height = metrics.underlineSize;
decPt.y = (frameTop - dec.mBaselineOffset) / app;
bCoord = (frameBStart - dec.mBaselineOffset) / app;
PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
metrics.underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
dec.mStyle, eNormalDecoration, aCallbacks, vertical);
dec.mStyle, eNormalDecoration, aCallbacks, verticalRun);
}
// Overlines
for (uint32_t i = aDecorations.mOverlines.Length(); i-- > 0; ) {
@ -6203,15 +6260,15 @@ nsTextFrame::DrawTextRunAndDecorations(
GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
const gfxFont::Metrics metrics =
GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
vertical);
useVerticalMetrics);
decSize.height = metrics.underlineSize;
decPt.y = (frameTop - dec.mBaselineOffset) / app;
bCoord = (frameBStart - dec.mBaselineOffset) / app;
PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
metrics.maxAscent, NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, dec.mStyle,
eNormalDecoration, aCallbacks, vertical);
eNormalDecoration, aCallbacks, verticalRun);
}
// CSS 2.1 mandates that text be painted after over/underlines, and *then*
@ -6230,15 +6287,15 @@ nsTextFrame::DrawTextRunAndDecorations(
GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
const gfxFont::Metrics metrics =
GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
vertical);
useVerticalMetrics);
decSize.height = metrics.strikeoutSize;
decPt.y = (frameTop - dec.mBaselineOffset) / app;
bCoord = (frameBStart - dec.mBaselineOffset) / app;
PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
metrics.strikeoutOffset, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH,
dec.mStyle, eNormalDecoration, aCallbacks, vertical);
dec.mStyle, eNormalDecoration, aCallbacks, verticalRun);
}
}
@ -6437,9 +6494,12 @@ nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
GetFontSizeInflation());
gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
gfxFont* firstFont = fontGroup->GetFirstValidFont();
bool vertical = GetWritingMode().IsVertical();
WritingMode wm = GetWritingMode();
bool verticalRun = wm.IsVertical();
bool useVerticalMetrics = verticalRun && !wm.IsSideways();
const gfxFont::Metrics& metrics =
firstFont->GetMetrics(vertical ? gfxFont::eVertical : gfxFont::eHorizontal);
firstFont->GetMetrics(useVerticalMetrics ? gfxFont::eVertical
: gfxFont::eHorizontal);
gfxFloat underlineOffset = fontGroup->GetUnderlineOffset();
gfxFloat ascent = aPresContext->AppUnitsToGfxUnits(mAscent);
gfxFloat descentLimit =
@ -6484,7 +6544,7 @@ nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
decorationArea =
nsCSSRendering::GetTextDecorationRect(aPresContext, size,
ascent, underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
style, vertical, descentLimit);
style, verticalRun, descentLimit);
aRect.UnionRect(aRect, decorationArea);
}
DestroySelectionDetails(details);
@ -7596,7 +7656,11 @@ nsTextFrame::ComputeTightBounds(gfxContext* aContext) const
aContext, &provider);
// mAscent should be the same as metrics.mAscent, but it's what we use to
// paint so that's the one we'll use.
nsRect boundingBox = RoundOut(metrics.mBoundingBox) + nsPoint(0, mAscent);
nsRect boundingBox = RoundOut(metrics.mBoundingBox);
if (GetWritingMode().IsLineInverted()) {
boundingBox.y = -boundingBox.YMost();
}
boundingBox += nsPoint(0, mAscent);
if (mTextRun->IsVertical()) {
// Swap line-relative textMetrics dimensions to physical coordinates.
Swap(boundingBox.x, boundingBox.y);
@ -8281,9 +8345,15 @@ nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
finalSize.BSize(wm) = 0;
} else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) {
// Use actual text metrics for floating first letter frame.
aMetrics.SetBlockStartAscent(NSToCoordCeil(textMetrics.mAscent));
finalSize.BSize(wm) = aMetrics.BlockStartAscent() +
NSToCoordCeil(textMetrics.mDescent);
if (wm.IsLineInverted()) {
aMetrics.SetBlockStartAscent(NSToCoordCeil(textMetrics.mDescent));
finalSize.BSize(wm) = aMetrics.BlockStartAscent() +
NSToCoordCeil(textMetrics.mAscent);
} else {
aMetrics.SetBlockStartAscent(NSToCoordCeil(textMetrics.mAscent));
finalSize.BSize(wm) = aMetrics.BlockStartAscent() +
NSToCoordCeil(textMetrics.mDescent);
}
} else {
// Otherwise, ascent should contain the overline drawable area.
// And also descent should contain the underline drawable area.
@ -8291,9 +8361,15 @@ nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
nsFontMetrics* fm = provider.GetFontMetrics();
nscoord fontAscent = fm->MaxAscent();
nscoord fontDescent = fm->MaxDescent();
aMetrics.SetBlockStartAscent(std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent));
nscoord descent = std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
finalSize.BSize(wm) = aMetrics.BlockStartAscent() + descent;
if (wm.IsLineInverted()) {
aMetrics.SetBlockStartAscent(std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent));
nscoord descent = std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent);
finalSize.BSize(wm) = aMetrics.BlockStartAscent() + descent;
} else {
aMetrics.SetBlockStartAscent(std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent));
nscoord descent = std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
finalSize.BSize(wm) = aMetrics.BlockStartAscent() + descent;
}
}
aMetrics.SetSize(wm, finalSize);
@ -8306,13 +8382,17 @@ nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
mAscent = aMetrics.BlockStartAscent();
// Handle text that runs outside its normal bounds.
nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent);
aMetrics.SetOverflowAreasToDesiredBounds();
nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
if (wm.IsLineInverted()) {
boundingBox.y = -boundingBox.YMost();
}
boundingBox += nsPoint(0, mAscent);
if (mTextRun->IsVertical()) {
// Swap line-relative textMetrics dimensions to physical coordinates.
Swap(boundingBox.x, boundingBox.y);
Swap(boundingBox.width, boundingBox.height);
}
aMetrics.SetOverflowAreasToDesiredBounds();
aMetrics.VisualOverflow().UnionRect(aMetrics.VisualOverflow(), boundingBox);
// When we have text decorations, we don't need to compute their overflow now
@ -8528,18 +8608,23 @@ nsTextFrame::RecomputeOverflow(const nsHTMLReflowState& aBlockReflowState)
ComputeTransformedLength(provider),
gfxFont::LOOSE_INK_EXTENTS, nullptr,
&provider);
nsRect &vis = result.VisualOverflow();
nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent);
nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
if (GetWritingMode().IsLineInverted()) {
boundingBox.y = -boundingBox.YMost();
}
boundingBox += nsPoint(0, mAscent);
if (mTextRun->IsVertical()) {
// Swap line-relative textMetrics dimensions to physical coordinates.
Swap(boundingBox.x, boundingBox.y);
Swap(boundingBox.width, boundingBox.height);
}
nsRect &vis = result.VisualOverflow();
vis.UnionRect(vis, boundingBox);
UnionAdditionalOverflow(PresContext(), aBlockReflowState.frame, provider,
&vis, true);
return result;
}
static char16_t TransformChar(const nsStyleText* aStyle, gfxTextRun* aTextRun,
uint32_t aSkippedOffset, char16_t aChar)
{

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>Bug 1083892</title>
<style>
div {
width:300px;
height:300px;
padding:10px;
border:1px solid black;
writing-mode:vertical-rl;
}
</style>
</head>
<body>
<div>
This is the <b><i>first</i> paragraph</b>. It's long enough to wrap onto multiple lines.<br>
<b>Paragraph <i>two</i></b>.<br>
<b><i>Third and final</i> paragraph</b> of this simple testcase. That's all, folks!
</div>
</body>
</html>

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<title>Bug 1083892</title>
<style>
div {
width:300px;
height:300px;
padding:10px;
border:1px solid black;
writing-mode:vertical-rl;
}
p {
margin: 0;
}
</style>
</head>
<body>
<div>
<p>This is the <b><i>first</i> paragraph</b>. It's long enough to wrap onto multiple lines.
<p><b>Paragraph <i>two</i></b>.
<p><b><i>Third and final</i> paragraph</b> of this simple testcase. That's all, folks!
</div>
</body>
</html>

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.v-lr { writing-mode:vertical-lr; }
.v-rl { writing-mode:vertical-rl; }
div {
width: 300px;
height: 200px;
background: #ddd;
margin: 50px;
}
</style>
</head>
<body>
<div class="v-lr">
First part of the block.
<i id="test">New text inserted by script, to cause a reflow that slides the following lines.</i>
We will insert enough new content that it wraps onto additional lines.
<br><br>
Here is some more text that follows a forced break.
Observe what happens to it when text is added earlier.
</div>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html class="reftest-wait">
<head>
<meta charset="utf-8">
<style>
.v-lr { writing-mode:vertical-lr; }
.v-rl { writing-mode:vertical-rl; }
div {
width: 300px;
height: 200px;
background: #ddd;
margin: 50px;
}
</style>
<script>
function doTest() {
document.getElementById("test").textContent =
"New text inserted by script, to cause a reflow that slides the following lines.";
document.documentElement.removeAttribute("class");
}
</script>
</head>
<body onload="doTest()">
<div class="v-lr">
First part of the block.
<i id="test"></i>
We will insert enough new content that it wraps onto additional lines.
<br><br>
Here is some more text that follows a forced break.
Observe what happens to it when text is added earlier.
</div>
</body>
</html>

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.v-lr { writing-mode:vertical-lr; }
.v-rl { writing-mode:vertical-rl; }
div {
width: 300px;
height: 200px;
background: #ddd;
margin: 50px;
}
</style>
</head>
<body>
<div class="v-rl">
First part of the block.
<i id="test">New text inserted by script, to cause a reflow that slides the following lines.</i>
We will insert enough new content that it wraps onto additional lines.
<br><br>
Here is some more text that follows a forced break.
Observe what happens to it when text is added earlier.
</div>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html class="reftest-wait">
<head>
<meta charset="utf-8">
<style>
.v-lr { writing-mode:vertical-lr; }
.v-rl { writing-mode:vertical-rl; }
div {
width: 300px;
height: 200px;
background: #ddd;
margin: 50px;
}
</style>
<script>
function doTest() {
document.getElementById("test").textContent =
"New text inserted by script, to cause a reflow that slides the following lines.";
document.documentElement.removeAttribute("class");
}
</script>
</head>
<body onload="doTest()">
<div class="v-rl">
First part of the block.
<i id="test"></i>
We will insert enough new content that it wraps onto additional lines.
<br><br>
Here is some more text that follows a forced break.
Observe what happens to it when text is added earlier.
</div>
</body>
</html>

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.h { writing-mode:horizontal-tb; }
.v-lr { writing-mode:vertical-lr; text-orientation:sideways-right; }
.v-rl { writing-mode:vertical-rl; text-orientation:sideways-right; }
div {
width: 250px;
height: 250px;
border: 1px solid red;
margin: 10px;
display: inline-block;
font: 16px monospace;
line-height: 32px;
}
</style>
</head>
<body>
<div class="h">
你好吗? hello
</div>
<div class="v-lr">
你好吗? hello
</div>
<div class="v-rl">
你好吗? hello
</div>
</body>
</html>

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.h { writing-mode:horizontal-tb; }
.v-lr { writing-mode:vertical-lr; text-orientation:sideways-right; }
.v-rl { writing-mode:vertical-rl; text-orientation:sideways-right; }
div {
width: 250px;
height: 250px;
border: 1px solid red;
margin: 10px;
display: inline-block;
font: 16px monospace;
line-height: 32px;
}
</style>
</head>
<body>
<div class="h">
你好吗? <span>hello</span>
</div>
<div class="v-lr">
你好吗? <span>hello</span>
</div>
<div class="v-rl">
你好吗? <span>hello</span>
</div>
</body>
</html>

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
var testFont = '40px sans-serif';
function test(x, y, text, style, rotation) {
var canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 400;
canvas.style.cssText = 'position:absolute;' + style;
document.getElementsByTagName('body')[0].appendChild(canvas);
var ctx = canvas.getContext('2d');
ctx.globalAlpha = 0.5;
ctx.beginPath();
ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y);
ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20);
ctx.stroke();
ctx.globalAlpha = 1.0;
ctx.font = testFont;
if (rotation != 0) {
ctx.translate(x,y);
ctx.rotate(rotation);
ctx.translate(-x,-y);
}
ctx.fillText(text, x, y);
}
// Testcase: vertical text with orientation:sideways-right
// test(100, 50, 'Hello', 'writing-mode:vertical-lr;text-orientation:sideways-right', 0);
// Reference: horizontal text with 90° rotation
// test(100, 50, 'Hello', 'writing-mode:horizontal-tb', Math.PI/2);
// Non-reference: vertical text with orientation:mixed
test(100, 50, 'Hello', 'writing-mode:vertical-lr;text-orientation:mixed', 0);
</script>

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
var testFont = '40px sans-serif';
function test(x, y, text, style, rotation) {
var canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 400;
canvas.style.cssText = 'position:absolute;' + style;
document.getElementsByTagName('body')[0].appendChild(canvas);
var ctx = canvas.getContext('2d');
ctx.globalAlpha = 0.5;
ctx.beginPath();
ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y);
ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20);
ctx.stroke();
ctx.globalAlpha = 1.0;
ctx.font = testFont;
if (rotation != 0) {
ctx.translate(x,y);
ctx.rotate(rotation);
ctx.translate(-x,-y);
}
ctx.fillText(text, x, y);
}
// Testcase: vertical text with orientation:sideways-right
// test(100, 50, 'Hello', 'writing-mode:vertical-lr;text-orientation:sideways-right', 0);
// Reference: horizontal text with 90° rotation
test(100, 50, 'Hello', 'writing-mode:horizontal-tb', Math.PI/2);
// Non-reference: vertical text with orientation:mixed
// test(100, 50, 'Hello', 'writing-mode:vertical-lr;text-orientation:mixed', 0);
</script>

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
var testFont = '40px sans-serif';
function test(x, y, text, style, rotation) {
var canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 400;
canvas.style.cssText = 'position:absolute;' + style;
document.getElementsByTagName('body')[0].appendChild(canvas);
var ctx = canvas.getContext('2d');
ctx.globalAlpha = 0.5;
ctx.beginPath();
ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y);
ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20);
ctx.stroke();
ctx.globalAlpha = 1.0;
ctx.font = testFont;
if (rotation != 0) {
ctx.translate(x,y);
ctx.rotate(rotation);
ctx.translate(-x,-y);
}
ctx.fillText(text, x, y);
}
// Testcase: vertical text with orientation:sideways-right
test(100, 50, 'Hello', 'writing-mode:vertical-lr;text-orientation:sideways-right', 0);
// Reference: horizontal text with 90° rotation
// test(100, 50, 'Hello', 'writing-mode:horizontal-tb', Math.PI/2);
// Non-reference: vertical text with orientation:mixed
// test(100, 50, 'Hello', 'writing-mode:vertical-lr;text-orientation:mixed', 0);
</script>

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
var testFont = '40px sans-serif';
function test(x, y, text, style, rotation, baseline) {
var canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 400;
canvas.style.cssText = 'position:absolute;' + style;
document.getElementsByTagName('body')[0].appendChild(canvas);
var ctx = canvas.getContext('2d');
ctx.globalAlpha = 0.5;
ctx.beginPath();
ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y);
ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20);
ctx.stroke();
ctx.globalAlpha = 1.0;
ctx.font = testFont;
if (rotation != 0) {
ctx.translate(x,y);
ctx.rotate(rotation);
ctx.translate(-x,-y);
}
if (baseline != '') {
ctx.textBaseline = baseline;
}
ctx.fillText(text, x, y);
}
// Testcase: vertical text with orientation:mixed
// test(100, 50, 'Hello', 'writing-mode:vertical-lr;text-orientation:mixed', 0, '');
// Reference: horizontal text with 90° rotation and textBaseline=middle
test(100, 50, 'Hello', 'writing-mode:horizontal-tb', Math.PI/2, 'middle');
</script>

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
var testFont = '40px sans-serif';
function test(x, y, text, style, rotation, baseline) {
var canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 400;
canvas.style.cssText = 'position:absolute;' + style;
document.getElementsByTagName('body')[0].appendChild(canvas);
var ctx = canvas.getContext('2d');
ctx.globalAlpha = 0.5;
ctx.beginPath();
ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y);
ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20);
ctx.stroke();
ctx.globalAlpha = 1.0;
ctx.font = testFont;
if (rotation != 0) {
ctx.translate(x,y);
ctx.rotate(rotation);
ctx.translate(-x,-y);
}
if (baseline != '') {
ctx.textBaseline = baseline;
}
ctx.fillText(text, x, y);
}
// Testcase: vertical text with orientation:mixed
test(100, 50, 'Hello', 'writing-mode:vertical-lr;text-orientation:mixed', 0, '');
// Reference: horizontal text with 90° rotation and textBaseline=middle
// test(100, 50, 'Hello', 'writing-mode:horizontal-tb', Math.PI/2, 'middle');
</script>

View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
var testFont = '40px sans-serif';
function test(x, y, text, style, rotation, baseline) {
var canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 400;
canvas.style.cssText = 'position:absolute;' + style;
document.getElementsByTagName('body')[0].appendChild(canvas);
var ctx = canvas.getContext('2d');
ctx.globalAlpha = 0.5;
ctx.beginPath();
ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y);
ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20);
ctx.stroke();
ctx.globalAlpha = 1.0;
ctx.font = testFont;
if (rotation != 0) {
ctx.translate(x,y);
ctx.rotate(rotation);
ctx.translate(-x,-y);
}
if (baseline != '') {
ctx.textBaseline = baseline;
}
ctx.fillText(text, x, y);
}
// Testcase: vertical text with various textBaselines
// test(100, 50, 'Top', 'writing-mode:vertical-lr', 0, 'top');
// test(150, 50, 'Middle', 'writing-mode:vertical-lr', 0, 'middle');
// test(200, 50, 'Alphabetic', 'writing-mode:vertical-lr', 0, 'alphabetic');
// test(250, 50, 'Bottom', 'writing-mode:vertical-lr', 0, 'bottom');
// Reference: horizontal text with 90° rotation and the same baselines
test(100, 50, 'Top', 'writing-mode:horizontal-tb', Math.PI/2, 'top');
test(150, 50, 'Middle', 'writing-mode:horizontal-tb', Math.PI/2, 'middle');
test(200, 50, 'Alphabetic', 'writing-mode:horizontal-tb', Math.PI/2, 'alphabetic');
test(250, 50, 'Bottom', 'writing-mode:horizontal-tb', Math.PI/2, 'bottom');
</script>

View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript">
var testFont = '40px sans-serif';
function test(x, y, text, style, rotation, baseline) {
var canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 400;
canvas.style.cssText = 'position:absolute;' + style;
document.getElementsByTagName('body')[0].appendChild(canvas);
var ctx = canvas.getContext('2d');
ctx.globalAlpha = 0.5;
ctx.beginPath();
ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y);
ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20);
ctx.stroke();
ctx.globalAlpha = 1.0;
ctx.font = testFont;
if (rotation != 0) {
ctx.translate(x,y);
ctx.rotate(rotation);
ctx.translate(-x,-y);
}
if (baseline != '') {
ctx.textBaseline = baseline;
}
ctx.fillText(text, x, y);
}
// Testcase: vertical text with various textBaselines
test(100, 50, 'Top', 'writing-mode:vertical-lr', 0, 'top');
test(150, 50, 'Middle', 'writing-mode:vertical-lr', 0, 'middle');
test(200, 50, 'Alphabetic', 'writing-mode:vertical-lr', 0, 'alphabetic');
test(250, 50, 'Bottom', 'writing-mode:vertical-lr', 0, 'bottom');
// Reference: horizontal text with 90° rotation and the same baselines
// test(100, 50, 'Top', 'writing-mode:horizontal-tb', Math.PI/2, 'top');
// test(150, 50, 'Middle', 'writing-mode:horizontal-tb', Math.PI/2, 'middle');
// test(200, 50, 'Alphabetic', 'writing-mode:horizontal-tb', Math.PI/2, 'alphabetic');
// test(250, 50, 'Bottom', 'writing-mode:horizontal-tb', Math.PI/2, 'bottom');
</script>

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.h { writing-mode:horizontal-tb; }
.v-lr { writing-mode:vertical-lr; }
.v-rl { writing-mode:vertical-rl; }
div {
width: 250px;
height: 250px;
border: 1px solid red;
margin: 10px;
display: inline-block;
}
</style>
</head>
<body>
<div class="h">
<u>方ABC方方</u><i><u>abc</u></i><u>方方方</u><b><u>xyz</u></b><u></u>
</div>
<div class="v-lr">
<u>方ABC方方</u><i><u>abc</u></i><u>方方方</u><b><u>xyz</u></b><u></u>
</div>
<div class="v-rl">
<u>方ABC方方</u><i><u>abc</u></i><u>方方方</u><b><u>xyz</u></b><u></u>
</div>
</body>
</html>

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.h { writing-mode:horizontal-tb; }
.v-lr { writing-mode:vertical-lr; }
.v-rl { writing-mode:vertical-rl; }
div {
width: 250px;
height: 250px;
border: 1px solid red;
margin: 10px;
display: inline-block;
}
</style>
</head>
<body>
<div class="h">
<u>方ABC方方<i>abc</i>方方方<b>xyz</b></u>
</div>
<div class="v-lr">
<u>方ABC方方<i>abc</i>方方方<b>xyz</b></u>
</div>
<div class="v-rl">
<u>方ABC方方<i>abc</i>方方方<b>xyz</b></u>
</div>
</body>
</html>

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
#outer {
margin: 0px;
background: #ddd;
font-size: 0;
width: 300px;
}
#test1 {
writing-mode: horizontal-tb;
height: 50px;
width: 100px;
margin-top: 10px;
margin-bottom: 0px;
margin-left: 50px;
margin-right: 0px;
border-top: 10px solid blue;
border-left: 20px solid red;
border-right: 30px solid green;
border-bottom: 40px solid gray;
display: inline-block;
background: yellow;
}
</style>
</head>
<body>
<div id="outer">
<div id="test1">
</div>
</div>
</body>
</html>

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