mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-16 23:05:42 +00:00
Merge from mozilla-inbound.
This commit is contained in:
commit
963211c18b
@ -329,14 +329,18 @@ bool
|
||||
nsTextEquivUtils::AppendString(nsAString *aString,
|
||||
const nsAString& aTextEquivalent)
|
||||
{
|
||||
// Insert spaces to insure that words from controls aren't jammed together.
|
||||
if (aTextEquivalent.IsEmpty())
|
||||
return false;
|
||||
|
||||
if (!aString->IsEmpty())
|
||||
// Insert spaces to insure that words from controls aren't jammed together.
|
||||
if (!aString->IsEmpty() && !IsWhitespace(aString->Last()))
|
||||
aString->Append(PRUnichar(' '));
|
||||
|
||||
aString->Append(aTextEquivalent);
|
||||
|
||||
if (!IsWhitespace(aString->Last()))
|
||||
aString->Append(PRUnichar(' '));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -91,6 +91,9 @@
|
||||
// Gets the name from html:input value, ignore @title attribute on input
|
||||
testName("from_input_ignoretitle", "Custom country");
|
||||
|
||||
// Insert spaces around the control's value to not jamm sibling text nodes
|
||||
testName("insert_spaces_around_control", "start value end");
|
||||
|
||||
// Gets the name from @title, ignore whitespace content
|
||||
testName("from_label_ignore_ws_subtree", "about");
|
||||
|
||||
@ -262,6 +265,11 @@
|
||||
title="ARIA slider and spinbutton don't provide a value for name computation">
|
||||
Bug 812041
|
||||
</a>
|
||||
<a target="_blank"
|
||||
href="https://bugzilla.mozilla.org/show_bug.cgi?id=823927"
|
||||
title="Text is jammed with control's text in name computation">
|
||||
Bug 823927
|
||||
</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test">
|
||||
@ -389,6 +397,11 @@
|
||||
value="Custom country"
|
||||
title="Input your country of origin"/ >
|
||||
|
||||
<!-- name from subtree, surround control by spaces to not jamm the text -->
|
||||
<label id="insert_spaces_around_control">
|
||||
start<input value="value">end
|
||||
</label>
|
||||
|
||||
<!-- no name from subtree because it holds whitespaces only -->
|
||||
<a id="from_label_ignore_ws_subtree" href="about:" title="about"> </a>
|
||||
|
||||
|
@ -287,9 +287,6 @@
|
||||
@BINPATH@/components/services-crypto.xpt
|
||||
#endif
|
||||
@BINPATH@/components/services-crypto-component.xpt
|
||||
#ifdef MOZ_SERVICES_CAPTIVEDETECT
|
||||
@BINPATH@/components/services-captivedetect.xpt
|
||||
#endif
|
||||
@BINPATH@/components/shellservice.xpt
|
||||
@BINPATH@/components/shistory.xpt
|
||||
@BINPATH@/components/spellchecker.xpt
|
||||
@ -496,10 +493,6 @@
|
||||
@BINPATH@/components/HealthReportComponents.manifest
|
||||
@BINPATH@/components/HealthReportService.js
|
||||
#endif
|
||||
#ifdef MOZ_SERVICES_CAPTIVEDETECT
|
||||
@BINPATH@/components/CaptivePortalDetectComponents.manifest
|
||||
@BINPATH@/components/captivedetect.js
|
||||
#endif
|
||||
@BINPATH@/components/TelemetryPing.js
|
||||
@BINPATH@/components/TelemetryPing.manifest
|
||||
@BINPATH@/components/Webapps.js
|
||||
@ -597,9 +590,6 @@
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
@BINPATH@/defaults/pref/services-sync.js
|
||||
#endif
|
||||
#ifdef MOZ_SERVICES_CAPTIVEDETECT
|
||||
@BINPATH@/defaults/pref/services-captivedetect.js
|
||||
#endif
|
||||
|
||||
; [Layout Engine Resources]
|
||||
; Style Sheets, Graphics and other Resources used by the layout engine.
|
||||
|
@ -18,10 +18,10 @@ function onLoad(event) {
|
||||
gProgressBar = document.getElementById('uploadProgressBar');
|
||||
|
||||
if (Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
|
||||
gProgressBar.style.display = "inline";
|
||||
gProgressBar.hidden = false;
|
||||
}
|
||||
else {
|
||||
gProgressBar.style.display = "none";
|
||||
gProgressBar.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,8 @@ let gTests = [ {
|
||||
let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
|
||||
let progressBar = doc.getElementById("uploadProgressBar");
|
||||
|
||||
isnot(progressBar.style.display, "none", "progress bar should be visible");
|
||||
let win = doc.defaultView;
|
||||
isnot(win.getComputedStyle(progressBar).display, "none", "progress bar should be visible");
|
||||
executeSoon(runNextTest);
|
||||
}
|
||||
},
|
||||
@ -30,7 +31,8 @@ let gTests = [ {
|
||||
let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
|
||||
let progressBar = doc.getElementById("uploadProgressBar");
|
||||
|
||||
is(progressBar.style.display, "none",
|
||||
let win = doc.defaultView;
|
||||
is(win.getComputedStyle(progressBar).display, "none",
|
||||
"progress bar should not be visible");
|
||||
executeSoon(runNextTest);
|
||||
}
|
||||
|
@ -286,9 +286,6 @@
|
||||
@BINPATH@/components/saxparser.xpt
|
||||
@BINPATH@/browser/components/sessionstore.xpt
|
||||
@BINPATH@/components/services-crypto-component.xpt
|
||||
#ifdef MOZ_SERVICES_CAPTIVEDETECT
|
||||
@BINPATH@/components/services-captivedetect.xpt
|
||||
#endif
|
||||
@BINPATH@/browser/components/shellservice.xpt
|
||||
@BINPATH@/components/shistory.xpt
|
||||
@BINPATH@/components/spellchecker.xpt
|
||||
@ -487,10 +484,6 @@
|
||||
@BINPATH@/components/SyncComponents.manifest
|
||||
@BINPATH@/components/Weave.js
|
||||
#endif
|
||||
#ifdef MOZ_SERVICES_CAPTIVEDETECT
|
||||
@BINPATH@/components/CaptivePortalDetectComponents.manifest
|
||||
@BINPATH@/components/captivedetect.js
|
||||
#endif
|
||||
@BINPATH@/components/servicesComponents.manifest
|
||||
@BINPATH@/components/cryptoComponents.manifest
|
||||
@BINPATH@/components/TelemetryPing.js
|
||||
@ -573,10 +566,12 @@
|
||||
#ifndef XP_MACOSX
|
||||
; shell icons
|
||||
@BINPATH@/browser/icons/*.png
|
||||
#ifdef MOZ_UPDATER
|
||||
; updater icon
|
||||
@BINPATH@/icons/updater.png
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
; [Default Preferences]
|
||||
; All the pref files must be part of base to prevent migration bugs
|
||||
|
@ -976,7 +976,6 @@ xpicleanup@BIN_SUFFIX@
|
||||
defaults/pref/firefox.js
|
||||
defaults/pref/firefox-l10n.js
|
||||
defaults/pref/services-sync.js
|
||||
defaults/pref/services-captivedetect.js
|
||||
defaults/profile/bookmarks.html
|
||||
defaults/profile/chrome/userChrome-example.css
|
||||
defaults/profile/chrome/userContent-example.css
|
||||
|
@ -2174,6 +2174,10 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
||||
.tab-throbber[progress] {
|
||||
list-style-image: url("chrome://browser/skin/tabbrowser/loading@2x.png");
|
||||
}
|
||||
|
||||
.tab-icon-image {
|
||||
image-rendering: -moz-crisp-edges;
|
||||
}
|
||||
}
|
||||
|
||||
.tabbrowser-tab:not(:hover) > .tab-stack > .tab-content > .tab-icon-image:not([selected="true"]) {
|
||||
|
@ -442,6 +442,7 @@ user_pref("browser.startup.page", 0); // use about:blank, not browser.startup.ho
|
||||
user_pref("browser.ui.layout.tablet", 0); // force tablet UI off
|
||||
user_pref("dom.allow_scripts_to_close_windows", true);
|
||||
user_pref("dom.disable_open_during_load", false);
|
||||
user_pref("dom.experimental_forms", true); // on for testing
|
||||
user_pref("dom.max_script_run_time", 0); // no slow script dialogs
|
||||
user_pref("hangmonitor.timeout", 0); // no hang monitor
|
||||
user_pref("dom.max_chrome_script_run_time", 0);
|
||||
|
@ -7,19 +7,26 @@ package @ANDROID_PACKAGE_NAME@;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.test.InstrumentationTestRunner;
|
||||
import android.util.Log;
|
||||
|
||||
public class FennecInstrumentationTestRunner extends InstrumentationTestRunner {
|
||||
private static Bundle sArguments;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle arguments) {
|
||||
super.onCreate(arguments);
|
||||
sArguments = arguments;
|
||||
if (sArguments == null) {
|
||||
Log.e("Robocop", "FennecInstrumentationTestRunner.onCreate got null bundle");
|
||||
}
|
||||
super.onCreate(arguments);
|
||||
}
|
||||
|
||||
// unfortunately we have to make this static because test classes that don't extend
|
||||
// from ActivityInstrumentationTestCase2 can't get a reference to this class.
|
||||
public static Bundle getArguments() {
|
||||
if (sArguments == null) {
|
||||
Log.e("Robocop", "FennecInstrumentationTestCase.getArguments returns null bundle");
|
||||
}
|
||||
return sArguments;
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,10 @@
|
||||
// No throwing exceptions
|
||||
#undef _STLP_NO_EXCEPTIONS
|
||||
#define _STLP_NO_EXCEPTIONS 1
|
||||
#undef _STLP_NO_EXCEPTION_HEADER
|
||||
#define _STLP_NO_EXCEPTION_HEADER 1
|
||||
#undef _STLP_NO_UNCAUGHT_EXCEPT_SUPPORT
|
||||
#define _STLP_NO_UNCAUGHT_EXCEPT_SUPPORT 1
|
||||
|
||||
#undef _STLP_NATIVE_CPP_C_HEADER
|
||||
#define _STLP_NATIVE_CPP_C_HEADER(header) <../../system/include/header>
|
||||
|
15
configure.in
15
configure.in
@ -4232,7 +4232,6 @@ MOZ_SAFE_BROWSING=
|
||||
MOZ_HELP_VIEWER=
|
||||
MOZ_SPELLCHECK=1
|
||||
MOZ_ANDROID_OMTC=
|
||||
MOZ_ANDROID_ANR_REPORTER=
|
||||
MOZ_ONLY_TOUCH_EVENTS=
|
||||
MOZ_TOOLKIT_SEARCH=1
|
||||
MOZ_UI_LOCALE=en-US
|
||||
@ -5087,13 +5086,6 @@ if test -n "$MOZ_ANDROID_OMTC"; then
|
||||
AC_DEFINE(MOZ_ANDROID_OMTC)
|
||||
fi
|
||||
|
||||
dnl ========================================================
|
||||
dnl = Android App Not Responding (ANR) reporter
|
||||
dnl ========================================================
|
||||
if test -n "$MOZ_ANDROID_ANR_REPORTER"; then
|
||||
AC_DEFINE(MOZ_ANDROID_ANR_REPORTER)
|
||||
fi
|
||||
|
||||
dnl ========================================================
|
||||
dnl = Disable WebSMS backend
|
||||
dnl ========================================================
|
||||
@ -8388,12 +8380,6 @@ if test -n "$MOZ_SERVICES_SYNC"; then
|
||||
AC_DEFINE(MOZ_SERVICES_SYNC)
|
||||
fi
|
||||
|
||||
dnl Build Captive Portal Detector if required
|
||||
AC_SUBST(MOZ_SERVICES_CAPTIVEDETECT)
|
||||
if test -n "$MOZ_SERVICES_CAPTIVEDETECT"; then
|
||||
AC_DEFINE(MOZ_SERVICES_CAPTIVEDETECT)
|
||||
fi
|
||||
|
||||
dnl ========================================================
|
||||
if test "$MOZ_DEBUG" -o "$NS_TRACE_MALLOC" -o "$MOZ_DMD"; then
|
||||
MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS=
|
||||
@ -8476,7 +8462,6 @@ AC_SUBST(MOZ_UNIVERSALCHARDET)
|
||||
AC_SUBST(ACCESSIBILITY)
|
||||
AC_SUBST(MOZ_SPELLCHECK)
|
||||
AC_SUBST(MOZ_ANDROID_OMTC)
|
||||
AC_SUBST(MOZ_ANDROID_ANR_REPORTER)
|
||||
AC_SUBST(MOZ_ONLY_TOUCH_EVENTS)
|
||||
AC_SUBST(MOZ_CRASHREPORTER)
|
||||
AC_SUBST(MOZ_CRASHREPORTER_INJECTOR)
|
||||
|
@ -1133,8 +1133,6 @@ nsFrameScriptExecutor::InitTabChildGlobalInternal(nsISupports* aScope)
|
||||
JS_SetVersion(cx, JSVERSION_LATEST);
|
||||
JS_SetErrorReporter(cx, ContentScriptErrorReporter);
|
||||
|
||||
xpc_LocalizeContext(cx);
|
||||
|
||||
JSAutoRequest ar(cx);
|
||||
nsIXPConnect* xpc = nsContentUtils::XPConnect();
|
||||
const uint32_t flags = nsIXPConnect::INIT_JS_STANDARD_CLASSES;
|
||||
|
@ -213,7 +213,8 @@ IsScopedStyleElement(nsIContent* aContent)
|
||||
// This is quicker than, say, QIing aContent to nsStyleLinkElement
|
||||
// and then calling its virtual GetStyleSheetInfo method to find out
|
||||
// if it is scoped.
|
||||
return aContent->IsHTML(nsGkAtoms::style) &&
|
||||
return (aContent->IsHTML(nsGkAtoms::style) ||
|
||||
aContent->IsSVG(nsGkAtoms::style)) &&
|
||||
aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::scoped);
|
||||
}
|
||||
|
||||
|
@ -34,8 +34,8 @@ XPCSHELL_TESTS += unit_ipc
|
||||
endif
|
||||
|
||||
|
||||
# Split files arbitrarily in two groups to not run into too-long command lines
|
||||
# which break on Windows (see bug 563151)
|
||||
# Split files arbitrarily in three groups to not run into too-long command lines
|
||||
# which break on Windows (see bug 563151 and bug 831989)
|
||||
MOCHITEST_FILES_A = \
|
||||
responseIdentical.sjs \
|
||||
test_bug5141.html \
|
||||
@ -508,6 +508,9 @@ MOCHITEST_FILES_B = \
|
||||
accesscontrol.resource^headers^ \
|
||||
invalid_accesscontrol.resource \
|
||||
invalid_accesscontrol.resource^headers^ \
|
||||
$(NULL)
|
||||
|
||||
MOCHITEST_FILES_C= \
|
||||
test_xhr_progressevents.html \
|
||||
progressserver.sjs \
|
||||
somedatas.resource \
|
||||
@ -655,15 +658,16 @@ MOCHITEST_CHROME_FILES = \
|
||||
test_bug357450.js \
|
||||
$(NULL)
|
||||
|
||||
MOCHITEST_FILES_PARTS = $(foreach s,A B,MOCHITEST_FILES_$(s))
|
||||
|
||||
# This test fails on the Mac for some reason
|
||||
ifneq (,$(filter gtk2 windows,$(MOZ_WIDGET_TOOLKIT)))
|
||||
MOCHITEST_FILES_C = test_copyimage.html \
|
||||
MOCHITEST_FILES_C += \
|
||||
test_copyimage.html \
|
||||
$(NULL)
|
||||
MOCHITEST_FILES_PARTS += MOCHITEST_FILES_C
|
||||
endif
|
||||
|
||||
MOCHITEST_FILES_PARTS = $(foreach s,A B C,MOCHITEST_FILES_$(s))
|
||||
|
||||
|
||||
# Disabled for now. Mochitest isn't reliable enough for these.
|
||||
# test_bug444546.html \
|
||||
# bug444546.sjs \
|
||||
|
@ -3377,6 +3377,20 @@ CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx,
|
||||
return NS_ERROR_DOM_SYNTAX_ERR;
|
||||
}
|
||||
|
||||
IntRect srcRect(0, 0, mWidth, mHeight);
|
||||
IntRect destRect(aX, aY, aWidth, aHeight);
|
||||
IntRect srcReadRect = srcRect.Intersect(destRect);
|
||||
RefPtr<DataSourceSurface> readback;
|
||||
if (!srcReadRect.IsEmpty() && !mZero) {
|
||||
RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
|
||||
if (snapshot) {
|
||||
readback = snapshot->GetDataSurface();
|
||||
}
|
||||
if (!readback || !readback->GetData()) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
JSObject* darray = JS_NewUint8ClampedArray(aCx, len.value());
|
||||
if (!darray) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
@ -3389,25 +3403,14 @@ CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx,
|
||||
|
||||
uint8_t* data = JS_GetUint8ClampedArrayData(darray);
|
||||
|
||||
IntRect srcRect(0, 0, mWidth, mHeight);
|
||||
IntRect destRect(aX, aY, aWidth, aHeight);
|
||||
|
||||
IntRect srcReadRect = srcRect.Intersect(destRect);
|
||||
IntRect dstWriteRect = srcReadRect;
|
||||
dstWriteRect.MoveBy(-aX, -aY);
|
||||
|
||||
uint8_t* src = data;
|
||||
uint32_t srcStride = aWidth * 4;
|
||||
|
||||
RefPtr<DataSourceSurface> readback;
|
||||
if (!srcReadRect.IsEmpty()) {
|
||||
RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
|
||||
if (snapshot) {
|
||||
readback = snapshot->GetDataSurface();
|
||||
|
||||
srcStride = readback->Stride();
|
||||
src = readback->GetData() + srcReadRect.y * srcStride + srcReadRect.x * 4;
|
||||
}
|
||||
if (readback) {
|
||||
srcStride = readback->Stride();
|
||||
src = readback->GetData() + srcReadRect.y * srcStride + srcReadRect.x * 4;
|
||||
}
|
||||
|
||||
// NOTE! dst is the same as src, and this relies on reading
|
||||
|
@ -108,6 +108,10 @@ public:
|
||||
bool aNullParent = true);
|
||||
virtual void DoneCreatingElement();
|
||||
|
||||
virtual bool IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable,
|
||||
int32_t *aTabIndex);
|
||||
virtual int32_t TabIndexDefault();
|
||||
|
||||
/**
|
||||
* Call this to reevaluate whether we should start/stop due to our owner
|
||||
* document being active, inactive, visible or hidden.
|
||||
|
159
content/html/content/src/HTMLProgressElement.cpp
Normal file
159
content/html/content/src/HTMLProgressElement.cpp
Normal file
@ -0,0 +1,159 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/dom/HTMLProgressElement.h"
|
||||
#include "mozilla/dom/HTMLProgressElementBinding.h"
|
||||
|
||||
NS_IMPL_NS_NEW_HTML_ELEMENT(Progress)
|
||||
DOMCI_NODE_DATA(HTMLProgressElement, mozilla::dom::HTMLProgressElement)
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
const double HTMLProgressElement::kIndeterminatePosition = -1.0;
|
||||
const double HTMLProgressElement::kDefaultValue = 0.0;
|
||||
const double HTMLProgressElement::kDefaultMax = 1.0;
|
||||
|
||||
|
||||
HTMLProgressElement::HTMLProgressElement(already_AddRefed<nsINodeInfo> aNodeInfo)
|
||||
: nsGenericHTMLElement(aNodeInfo)
|
||||
{
|
||||
SetIsDOMBinding();
|
||||
|
||||
// We start out indeterminate
|
||||
AddStatesSilently(NS_EVENT_STATE_INDETERMINATE);
|
||||
}
|
||||
|
||||
HTMLProgressElement::~HTMLProgressElement()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(HTMLProgressElement, Element)
|
||||
NS_IMPL_RELEASE_INHERITED(HTMLProgressElement, Element)
|
||||
|
||||
|
||||
NS_INTERFACE_TABLE_HEAD(HTMLProgressElement)
|
||||
NS_HTML_CONTENT_INTERFACE_TABLE1(HTMLProgressElement,
|
||||
nsIDOMHTMLProgressElement)
|
||||
NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(HTMLProgressElement,
|
||||
nsGenericHTMLElement)
|
||||
NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLProgressElement)
|
||||
|
||||
NS_IMPL_ELEMENT_CLONE(HTMLProgressElement)
|
||||
|
||||
|
||||
nsEventStates
|
||||
HTMLProgressElement::IntrinsicState() const
|
||||
{
|
||||
nsEventStates state = nsGenericHTMLElement::IntrinsicState();
|
||||
|
||||
if (IsIndeterminate()) {
|
||||
state |= NS_EVENT_STATE_INDETERMINATE;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLProgressElement::ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute,
|
||||
const nsAString& aValue, nsAttrValue& aResult)
|
||||
{
|
||||
if (aNamespaceID == kNameSpaceID_None) {
|
||||
if (aAttribute == nsGkAtoms::value || aAttribute == nsGkAtoms::max) {
|
||||
return aResult.ParseDoubleValue(aValue);
|
||||
}
|
||||
}
|
||||
|
||||
return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute,
|
||||
aValue, aResult);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLProgressElement::GetValue(double* aValue)
|
||||
{
|
||||
*aValue = Value();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
double
|
||||
HTMLProgressElement::Value() const
|
||||
{
|
||||
const nsAttrValue* attrValue = mAttrsAndChildren.GetAttr(nsGkAtoms::value);
|
||||
if (!attrValue || attrValue->Type() != nsAttrValue::eDoubleValue ||
|
||||
attrValue->GetDoubleValue() < 0.0) {
|
||||
return kDefaultValue;
|
||||
}
|
||||
|
||||
return std::min(attrValue->GetDoubleValue(), Max());
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLProgressElement::SetValue(double aValue)
|
||||
{
|
||||
ErrorResult rv;
|
||||
SetValue(aValue, rv);
|
||||
return rv.ErrorCode();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLProgressElement::GetMax(double* aValue)
|
||||
{
|
||||
*aValue = Max();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
double
|
||||
HTMLProgressElement::Max() const
|
||||
{
|
||||
const nsAttrValue* attrMax = mAttrsAndChildren.GetAttr(nsGkAtoms::max);
|
||||
if (!attrMax || attrMax->Type() != nsAttrValue::eDoubleValue ||
|
||||
attrMax->GetDoubleValue() <= 0.0) {
|
||||
return kDefaultMax;
|
||||
}
|
||||
|
||||
return attrMax->GetDoubleValue();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLProgressElement::SetMax(double aValue)
|
||||
{
|
||||
ErrorResult rv;
|
||||
SetMax(aValue, rv);
|
||||
return rv.ErrorCode();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLProgressElement::GetPosition(double* aPosition)
|
||||
{
|
||||
*aPosition = Position();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
double
|
||||
HTMLProgressElement::Position() const
|
||||
{
|
||||
if (IsIndeterminate()) {
|
||||
return kIndeterminatePosition;
|
||||
}
|
||||
|
||||
return Value() / Max();
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLProgressElement::IsIndeterminate() const
|
||||
{
|
||||
const nsAttrValue* attrValue = mAttrsAndChildren.GetAttr(nsGkAtoms::value);
|
||||
return !attrValue || attrValue->Type() != nsAttrValue::eDoubleValue;
|
||||
}
|
||||
|
||||
JSObject*
|
||||
HTMLProgressElement::WrapNode(JSContext* aCx, JSObject* aScope,
|
||||
bool* aTriedToWrap)
|
||||
{
|
||||
return HTMLProgressElementBinding::Wrap(aCx, aScope, this, aTriedToWrap);
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
87
content/html/content/src/HTMLProgressElement.h
Normal file
87
content/html/content/src/HTMLProgressElement.h
Normal file
@ -0,0 +1,87 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_dom_HTMLProgressElement_h
|
||||
#define mozilla_dom_HTMLProgressElement_h
|
||||
|
||||
#include "nsIDOMHTMLProgressElement.h"
|
||||
#include "nsGenericHTMLElement.h"
|
||||
#include "nsAttrValue.h"
|
||||
#include "nsAttrValueInlines.h"
|
||||
#include "nsEventStateManager.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class HTMLProgressElement : public nsGenericHTMLElement,
|
||||
public nsIDOMHTMLProgressElement
|
||||
{
|
||||
public:
|
||||
HTMLProgressElement(already_AddRefed<nsINodeInfo> aNodeInfo);
|
||||
virtual ~HTMLProgressElement();
|
||||
|
||||
// nsISupports
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
// nsIDOMNode
|
||||
NS_FORWARD_NSIDOMNODE_TO_NSINODE
|
||||
|
||||
// nsIDOMElement
|
||||
NS_FORWARD_NSIDOMELEMENT_TO_GENERIC
|
||||
|
||||
// nsIDOMHTMLElement
|
||||
NS_FORWARD_NSIDOMHTMLELEMENT_TO_GENERIC
|
||||
|
||||
// nsIDOMHTMLProgressElement
|
||||
NS_DECL_NSIDOMHTMLPROGRESSELEMENT
|
||||
|
||||
nsEventStates IntrinsicState() const;
|
||||
|
||||
nsresult Clone(nsINodeInfo* aNodeInfo, nsINode** aResult) const;
|
||||
|
||||
bool ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute,
|
||||
const nsAString& aValue, nsAttrValue& aResult);
|
||||
|
||||
virtual nsXPCClassInfo* GetClassInfo();
|
||||
|
||||
virtual nsIDOMNode* AsDOMNode() { return this; }
|
||||
|
||||
// WebIDL
|
||||
double Value() const;
|
||||
void SetValue(double aValue, ErrorResult& aRv)
|
||||
{
|
||||
aRv = SetDoubleAttr(nsGkAtoms::value, aValue);
|
||||
}
|
||||
double Max() const;
|
||||
void SetMax(double aValue, ErrorResult& aRv)
|
||||
{
|
||||
aRv = SetDoubleAttr(nsGkAtoms::max, aValue);
|
||||
}
|
||||
double Position() const;
|
||||
|
||||
protected:
|
||||
virtual JSObject* WrapNode(JSContext* aCx, JSObject* aScope,
|
||||
bool* aTriedToWrap) MOZ_OVERRIDE;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Returns whethem the progress element is in the indeterminate state.
|
||||
* A progress element is in the indeterminate state if its value is ommited
|
||||
* or is not a floating point number..
|
||||
*
|
||||
* @return whether the progress element is in the indeterminate state.
|
||||
*/
|
||||
bool IsIndeterminate() const;
|
||||
|
||||
static const double kIndeterminatePosition;
|
||||
static const double kDefaultValue;
|
||||
static const double kDefaultMax;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_HTMLProgressElement_h
|
@ -53,6 +53,7 @@ EXPORTS_mozilla/dom = \
|
||||
HTMLOptGroupElement.h \
|
||||
HTMLParagraphElement.h \
|
||||
HTMLPreElement.h \
|
||||
HTMLProgressElement.h \
|
||||
HTMLScriptElement.h \
|
||||
HTMLSharedElement.h \
|
||||
HTMLSharedListElement.h \
|
||||
@ -114,7 +115,7 @@ CPPSRCS = \
|
||||
nsHTMLOutputElement.cpp \
|
||||
HTMLParagraphElement.cpp \
|
||||
HTMLPreElement.cpp \
|
||||
nsHTMLProgressElement.cpp \
|
||||
HTMLProgressElement.cpp \
|
||||
HTMLScriptElement.cpp \
|
||||
nsHTMLSelectElement.cpp \
|
||||
HTMLSharedElement.cpp \
|
||||
|
@ -358,8 +358,8 @@ public:
|
||||
* in aIsFocusable.
|
||||
*/
|
||||
virtual bool IsHTMLFocusable(bool aWithMouse,
|
||||
bool *aIsFocusable,
|
||||
int32_t *aTabIndex);
|
||||
bool *aIsFocusable,
|
||||
int32_t *aTabIndex);
|
||||
virtual void PerformAccesskey(bool aKeyCausesActivation,
|
||||
bool aIsTrustedEvent);
|
||||
|
||||
|
@ -2048,6 +2048,23 @@ void nsHTMLMediaElement::DoneCreatingElement()
|
||||
mMuted = true;
|
||||
}
|
||||
|
||||
bool nsHTMLMediaElement::IsHTMLFocusable(bool aWithMouse,
|
||||
bool *aIsFocusable,
|
||||
int32_t *aTabIndex)
|
||||
{
|
||||
if (nsGenericHTMLElement::IsHTMLFocusable(aWithMouse, aIsFocusable, aTabIndex)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
*aIsFocusable = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t nsHTMLMediaElement::TabIndexDefault()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
nsresult nsHTMLMediaElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
|
||||
nsIAtom* aPrefix, const nsAString& aValue,
|
||||
bool aNotify)
|
||||
|
@ -1,192 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsIDOMHTMLProgressElement.h"
|
||||
#include "nsGenericHTMLElement.h"
|
||||
#include "nsAttrValue.h"
|
||||
#include "nsAttrValueInlines.h"
|
||||
#include "nsEventStateManager.h"
|
||||
#include <algorithm>
|
||||
|
||||
using namespace mozilla::dom;
|
||||
|
||||
class nsHTMLProgressElement : public nsGenericHTMLElement,
|
||||
public nsIDOMHTMLProgressElement
|
||||
{
|
||||
public:
|
||||
nsHTMLProgressElement(already_AddRefed<nsINodeInfo> aNodeInfo);
|
||||
virtual ~nsHTMLProgressElement();
|
||||
|
||||
// nsISupports
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
// nsIDOMNode
|
||||
NS_FORWARD_NSIDOMNODE_TO_NSINODE
|
||||
|
||||
// nsIDOMElement
|
||||
NS_FORWARD_NSIDOMELEMENT_TO_GENERIC
|
||||
|
||||
// nsIDOMHTMLElement
|
||||
NS_FORWARD_NSIDOMHTMLELEMENT_TO_GENERIC
|
||||
|
||||
// nsIDOMHTMLProgressElement
|
||||
NS_DECL_NSIDOMHTMLPROGRESSELEMENT
|
||||
|
||||
nsEventStates IntrinsicState() const;
|
||||
|
||||
nsresult Clone(nsINodeInfo* aNodeInfo, nsINode** aResult) const;
|
||||
|
||||
bool ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute,
|
||||
const nsAString& aValue, nsAttrValue& aResult);
|
||||
|
||||
virtual nsXPCClassInfo* GetClassInfo();
|
||||
|
||||
virtual nsIDOMNode* AsDOMNode() { return this; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Returns whethem the progress element is in the indeterminate state.
|
||||
* A progress element is in the indeterminate state if its value is ommited
|
||||
* or is not a floating point number..
|
||||
*
|
||||
* @return whether the progress element is in the indeterminate state.
|
||||
*/
|
||||
bool IsIndeterminate() const;
|
||||
|
||||
static const double kIndeterminatePosition;
|
||||
static const double kDefaultValue;
|
||||
static const double kDefaultMax;
|
||||
};
|
||||
|
||||
const double nsHTMLProgressElement::kIndeterminatePosition = -1.0;
|
||||
const double nsHTMLProgressElement::kDefaultValue = 0.0;
|
||||
const double nsHTMLProgressElement::kDefaultMax = 1.0;
|
||||
|
||||
NS_IMPL_NS_NEW_HTML_ELEMENT(Progress)
|
||||
|
||||
|
||||
nsHTMLProgressElement::nsHTMLProgressElement(already_AddRefed<nsINodeInfo> aNodeInfo)
|
||||
: nsGenericHTMLElement(aNodeInfo)
|
||||
{
|
||||
// We start out indeterminate
|
||||
AddStatesSilently(NS_EVENT_STATE_INDETERMINATE);
|
||||
}
|
||||
|
||||
nsHTMLProgressElement::~nsHTMLProgressElement()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(nsHTMLProgressElement, Element)
|
||||
NS_IMPL_RELEASE_INHERITED(nsHTMLProgressElement, Element)
|
||||
|
||||
DOMCI_NODE_DATA(HTMLProgressElement, nsHTMLProgressElement)
|
||||
|
||||
NS_INTERFACE_TABLE_HEAD(nsHTMLProgressElement)
|
||||
NS_HTML_CONTENT_INTERFACE_TABLE1(nsHTMLProgressElement,
|
||||
nsIDOMHTMLProgressElement)
|
||||
NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(nsHTMLProgressElement,
|
||||
nsGenericHTMLElement)
|
||||
NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLProgressElement)
|
||||
|
||||
NS_IMPL_ELEMENT_CLONE(nsHTMLProgressElement)
|
||||
|
||||
|
||||
nsEventStates
|
||||
nsHTMLProgressElement::IntrinsicState() const
|
||||
{
|
||||
nsEventStates state = nsGenericHTMLElement::IntrinsicState();
|
||||
|
||||
if (IsIndeterminate()) {
|
||||
state |= NS_EVENT_STATE_INDETERMINATE;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
bool
|
||||
nsHTMLProgressElement::ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute,
|
||||
const nsAString& aValue, nsAttrValue& aResult)
|
||||
{
|
||||
if (aNamespaceID == kNameSpaceID_None) {
|
||||
if (aAttribute == nsGkAtoms::value || aAttribute == nsGkAtoms::max) {
|
||||
return aResult.ParseDoubleValue(aValue);
|
||||
}
|
||||
}
|
||||
|
||||
return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute,
|
||||
aValue, aResult);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHTMLProgressElement::GetValue(double* aValue)
|
||||
{
|
||||
const nsAttrValue* attrValue = mAttrsAndChildren.GetAttr(nsGkAtoms::value);
|
||||
if (!attrValue || attrValue->Type() != nsAttrValue::eDoubleValue ||
|
||||
attrValue->GetDoubleValue() < 0.0) {
|
||||
*aValue = kDefaultValue;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
*aValue = attrValue->GetDoubleValue();
|
||||
|
||||
double max;
|
||||
GetMax(&max);
|
||||
|
||||
*aValue = std::min(*aValue, max);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHTMLProgressElement::SetValue(double aValue)
|
||||
{
|
||||
return SetDoubleAttr(nsGkAtoms::value, aValue);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHTMLProgressElement::GetMax(double* aValue)
|
||||
{
|
||||
const nsAttrValue* attrMax = mAttrsAndChildren.GetAttr(nsGkAtoms::max);
|
||||
if (attrMax && attrMax->Type() == nsAttrValue::eDoubleValue &&
|
||||
attrMax->GetDoubleValue() > 0.0) {
|
||||
*aValue = attrMax->GetDoubleValue();
|
||||
} else {
|
||||
*aValue = kDefaultMax;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHTMLProgressElement::SetMax(double aValue)
|
||||
{
|
||||
return SetDoubleAttr(nsGkAtoms::max, aValue);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHTMLProgressElement::GetPosition(double* aPosition)
|
||||
{
|
||||
if (IsIndeterminate()) {
|
||||
*aPosition = kIndeterminatePosition;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
double value;
|
||||
double max;
|
||||
GetValue(&value);
|
||||
GetMax(&max);
|
||||
|
||||
*aPosition = value / max;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
nsHTMLProgressElement::IsIndeterminate() const
|
||||
{
|
||||
const nsAttrValue* attrValue = mAttrsAndChildren.GetAttr(nsGkAtoms::value);
|
||||
return !attrValue || attrValue->Type() != nsAttrValue::eDoubleValue;
|
||||
}
|
||||
|
@ -17,8 +17,6 @@
|
||||
|
||||
// TODO: maybe make those reflections be tested against all input types.
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
|
||||
// .accept
|
||||
reflectString({
|
||||
element: document.createElement("input"),
|
||||
@ -246,9 +244,6 @@ reflectUnsignedInt({
|
||||
// .validationMessage doesn't reflect a content attribute.
|
||||
// .labels doesn't reflect a content attribute.
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -308,8 +308,6 @@ function checkSanitizing(element)
|
||||
}
|
||||
}
|
||||
|
||||
var pref = SpecialPowers.getBoolPref("dom.experimental_forms");
|
||||
SpecialPowers.setBoolPref("dom.experimental_forms", true);
|
||||
for (type of inputTypes) {
|
||||
var form = document.forms[0];
|
||||
var element = document.createElement("input");
|
||||
@ -345,8 +343,6 @@ for (type of todoTypes) {
|
||||
form.removeChild(element);
|
||||
}
|
||||
|
||||
SpecialPowers.setBoolPref("dom.experimental_forms", pref);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -213,11 +213,7 @@ function runTest()
|
||||
var testRunner = runTest();
|
||||
|
||||
addLoadEvent(function () {
|
||||
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]},
|
||||
function() {
|
||||
testRunner.next();
|
||||
}
|
||||
);
|
||||
testRunner.next();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
@ -77,8 +77,6 @@ function checkValidity(aElement, aValidity, aApply, aRangeApply)
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
|
||||
for (var test of data) {
|
||||
input.type = test.type;
|
||||
var apply = test.apply;
|
||||
@ -288,9 +286,6 @@ for (var test of data) {
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -77,8 +77,6 @@ function checkValidity(aElement, aValidity, aApply, aRangeApply)
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
|
||||
for (var test of data) {
|
||||
input.type = test.type;
|
||||
var apply = test.apply;
|
||||
@ -294,9 +292,6 @@ for (var test of data) {
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -102,8 +102,6 @@ function checkMozIsTextFieldValueTodo(aInput, aResult)
|
||||
"mozIsTextField(true) should return " + aResult);
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
|
||||
// Check if the method is defined for the correct elements.
|
||||
for (data of gElementTestData) {
|
||||
checkMozIsTextFieldDefined(data[0], data[1]);
|
||||
@ -122,9 +120,6 @@ for (data of gInputTodoData) {
|
||||
checkMozIsTextFieldValueTodo(input, data[1]);
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -265,8 +265,6 @@ var invalidTypes = Array('checkbox', 'radio', 'file', 'number', 'date', 'time');
|
||||
// TODO: 'datetime', 'month', 'week', 'datetime-local', 'range' and 'color'
|
||||
// do not accept the @pattern too but are not implemented yet.
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
|
||||
for (type of validTypes) {
|
||||
input.type = type;
|
||||
completeValidityCheck(input, false);
|
||||
@ -283,9 +281,6 @@ for (type of invalidTypes) {
|
||||
completeValidityCheck(input, true);
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -356,9 +356,6 @@ function checkInputRequiredValidityForFile()
|
||||
checkNotSufferingFromBeingMissing(element);
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
|
||||
|
||||
checkTextareaRequiredValidity();
|
||||
|
||||
// The require attribute behavior depend of the input type.
|
||||
@ -390,9 +387,6 @@ checkInputRequiredValidityForCheckbox();
|
||||
checkInputRequiredValidityForRadio();
|
||||
checkInputRequiredValidityForFile();
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -83,8 +83,6 @@ function checkValidity(aElement, aValidity, aApply, aData)
|
||||
(aElement.wil && aValidity) ? ":invalid shouldn't apply" : "valid should apply");
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
|
||||
for (var test of data) {
|
||||
var input = getFreshElement(test.type);
|
||||
var apply = test.apply;
|
||||
@ -523,9 +521,6 @@ for (var test of data) {
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -601,18 +601,12 @@ function checkStepUp()
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
|
||||
|
||||
checkPresence();
|
||||
checkAvailability();
|
||||
|
||||
checkStepDown();
|
||||
checkStepUp();
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -285,8 +285,6 @@ function checkValidityStateObjectAliveWithoutElement(element)
|
||||
ok(v.valid, "When the element is not alive, it should be valid");
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
|
||||
checkConstraintValidationAPIExist(document.getElementById('f'));
|
||||
checkConstraintValidationAPIExist(document.getElementById('i'));
|
||||
checkConstraintValidationAPIExist(document.getElementById('b'));
|
||||
@ -341,9 +339,6 @@ checkValidityStateObjectAliveWithoutElement("textarea");
|
||||
checkValidityStateObjectAliveWithoutElement("output");
|
||||
checkValidityStateObjectAliveWithoutElement("object");
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -372,9 +372,6 @@ function checkWithBustedPrototype()
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
|
||||
|
||||
checkAvailability();
|
||||
checkGarbageValues();
|
||||
checkWithBustedPrototype();
|
||||
@ -387,9 +384,6 @@ checkDateSet();
|
||||
checkTimeGet();
|
||||
checkTimeSet();
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -396,8 +396,6 @@ function checkTimeSet()
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
|
||||
checkAvailability();
|
||||
|
||||
// <input type='number'> test
|
||||
@ -412,9 +410,6 @@ checkDateSet();
|
||||
checkTimeGet();
|
||||
checkTimeSet();
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -46,9 +46,6 @@ var todoTypes = [
|
||||
|
||||
var nonTrivialSanitizing = [ 'number', 'date', 'time' ];
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
|
||||
|
||||
var length = testData.length;
|
||||
for (var i=0; i<length; ++i) {
|
||||
for (var j=0; j<length; ++j) {
|
||||
@ -101,9 +98,6 @@ for (var type of todoTypes) {
|
||||
todo_is(e.type, type, type + " type isn't supported yet");
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
@ -52,6 +52,8 @@
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace dom;
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
extern PRLogModuleInfo* gMediaDecoderLog;
|
||||
#endif
|
||||
|
@ -1177,6 +1177,9 @@ this.DOMApplicationRegistry = {
|
||||
? updateSvc.scheduleCustomProfileUpdate(appcacheURI, docURI, aProfileDir)
|
||||
: updateSvc.scheduleAppUpdate(appcacheURI, docURI, aApp.localId, false);
|
||||
|
||||
// initialize the progress to 0 right now
|
||||
aApp.progress = 0;
|
||||
|
||||
// We save the download details for potential further usage like cancelling
|
||||
// it.
|
||||
let download = {
|
||||
@ -2050,6 +2053,13 @@ this.DOMApplicationRegistry = {
|
||||
AppDownloadManager.remove(aApp.manifestURL);
|
||||
}
|
||||
|
||||
function sendProgressEvent() {
|
||||
self.broadcastMessage("Webapps:PackageEvent",
|
||||
{ type: "progress",
|
||||
manifestURL: aApp.manifestURL,
|
||||
app: app });
|
||||
}
|
||||
|
||||
function download() {
|
||||
debug("About to download " + aManifest.fullPackagePath());
|
||||
|
||||
@ -2070,6 +2080,7 @@ this.DOMApplicationRegistry = {
|
||||
);
|
||||
|
||||
let lastProgressTime = 0;
|
||||
|
||||
requestChannel.notificationCallbacks = {
|
||||
QueryInterface: function notifQI(aIID) {
|
||||
if (aIID.equals(Ci.nsISupports) ||
|
||||
@ -2088,10 +2099,7 @@ this.DOMApplicationRegistry = {
|
||||
let now = Date.now();
|
||||
if (now - lastProgressTime > MIN_PROGRESS_EVENT_DELAY) {
|
||||
debug("onProgress: " + aProgress + "/" + aProgressMax);
|
||||
self.broadcastMessage("Webapps:PackageEvent",
|
||||
{ type: "progress",
|
||||
manifestURL: aApp.manifestURL,
|
||||
app: app });
|
||||
sendProgressEvent();
|
||||
lastProgressTime = now;
|
||||
self._saveApps();
|
||||
}
|
||||
@ -2117,6 +2125,9 @@ this.DOMApplicationRegistry = {
|
||||
// installed apps should morph to 'updating'.
|
||||
app.installState = aIsUpdate ? "updating" : "pending";
|
||||
|
||||
// initialize the progress to 0 right now
|
||||
app.progress = 0;
|
||||
|
||||
// Staging the zip in TmpD until all the checks are done.
|
||||
let zipFile = FileUtils.getFile("TmpD",
|
||||
["webapps", id, "application.zip"], true);
|
||||
@ -2327,6 +2338,9 @@ this.DOMApplicationRegistry = {
|
||||
});
|
||||
|
||||
requestChannel.asyncOpen(listener, null);
|
||||
|
||||
// send a first progress event to correctly set the DOM object's properties
|
||||
sendProgressEvent();
|
||||
};
|
||||
|
||||
let deviceStorage = Services.wm.getMostRecentWindow("navigator:browser")
|
||||
@ -2810,18 +2824,30 @@ let AppcacheObserver = function(aApp) {
|
||||
this.app = aApp;
|
||||
this.startStatus = aApp.installState;
|
||||
this.lastProgressTime = 0;
|
||||
// send a first progress event to correctly set the DOM object's properties
|
||||
this._sendProgressEvent();
|
||||
};
|
||||
|
||||
AppcacheObserver.prototype = {
|
||||
// nsIOfflineCacheUpdateObserver implementation
|
||||
_sendProgressEvent: function() {
|
||||
let app = this.app;
|
||||
DOMApplicationRegistry.broadcastMessage("Webapps:OfflineCache",
|
||||
{ manifest: app.manifestURL,
|
||||
installState: app.installState,
|
||||
progress: app.progress });
|
||||
},
|
||||
|
||||
updateStateChanged: function appObs_Update(aUpdate, aState) {
|
||||
let mustSave = false;
|
||||
let app = this.app;
|
||||
|
||||
debug("Offline cache state change for " + app.origin + " : " + aState);
|
||||
|
||||
var self = this;
|
||||
let setStatus = function appObs_setStatus(aStatus, aProgress) {
|
||||
debug("Offlinecache setStatus to " + aStatus + " for " + app.origin);
|
||||
debug("Offlinecache setStatus to " + aStatus + " with progress " +
|
||||
aProgress + " for " + app.origin);
|
||||
mustSave = (app.installState != aStatus);
|
||||
app.installState = aStatus;
|
||||
app.progress = aProgress;
|
||||
@ -2829,10 +2855,7 @@ AppcacheObserver.prototype = {
|
||||
app.downloading = false;
|
||||
app.downloadAvailable = false;
|
||||
}
|
||||
DOMApplicationRegistry.broadcastMessage("Webapps:OfflineCache",
|
||||
{ manifest: app.manifestURL,
|
||||
installState: app.installState,
|
||||
progress: app.progress });
|
||||
self._sendProgressEvent();
|
||||
}
|
||||
|
||||
let setError = function appObs_setError(aError) {
|
||||
|
@ -1125,8 +1125,6 @@ nsJSContext::nsJSContext(JSRuntime *aRuntime, bool aGCOnDestruction,
|
||||
js_options_dot_str, this);
|
||||
|
||||
::JS_SetOperationCallback(mContext, DOMOperationCallback);
|
||||
|
||||
xpc_LocalizeContext(mContext);
|
||||
}
|
||||
mIsInitialized = false;
|
||||
mTerminations = nullptr;
|
||||
|
@ -506,6 +506,10 @@ DOMInterfaces = {
|
||||
'hasInstanceInterface': 'nsIDOMHTMLPreElement',
|
||||
},
|
||||
|
||||
'HTMLProgressElement': {
|
||||
'hasInstanceInterface': 'nsIDOMHTMLProgressElement',
|
||||
},
|
||||
|
||||
'HTMLPropertiesCollection': {
|
||||
'headerFile': 'HTMLPropertiesCollection.h',
|
||||
'resultNotAddRefed': [ 'item', 'namedItem', 'names' ]
|
||||
|
@ -22,10 +22,7 @@ ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT))
|
||||
TEST_DIRS += tests
|
||||
endif
|
||||
|
||||
EXPORTS = \
|
||||
nsICachedFileDescriptorListener.h \
|
||||
PCOMContentPermissionRequestChild.h \
|
||||
$(NULL)
|
||||
EXPORTS = PCOMContentPermissionRequestChild.h
|
||||
|
||||
EXPORTS_NAMESPACES = \
|
||||
mozilla \
|
||||
|
@ -31,7 +31,6 @@ using gfxSize;
|
||||
using mozilla::layers::LayersBackend;
|
||||
using mozilla::layers::FrameMetrics;
|
||||
using mozilla::layout::ScrollingBehavior;
|
||||
using mozilla::void_t;
|
||||
using mozilla::WindowsHandle;
|
||||
using nscolor;
|
||||
using nsCompositionEvent;
|
||||
@ -291,8 +290,6 @@ child:
|
||||
|
||||
LoadURL(nsCString uri);
|
||||
|
||||
CacheFileDescriptor(nsString path, FileDescriptor fd);
|
||||
|
||||
UpdateDimensions(nsRect rect, nsIntSize size, ScreenOrientation orientation) compress;
|
||||
|
||||
UpdateFrame(FrameMetrics frame) compress;
|
||||
|
@ -18,7 +18,6 @@
|
||||
#include "mozilla/dom/PContentChild.h"
|
||||
#include "mozilla/dom/PContentDialogChild.h"
|
||||
#include "mozilla/ipc/DocumentRendererChild.h"
|
||||
#include "mozilla/ipc/FileDescriptorUtils.h"
|
||||
#include "mozilla/layers/AsyncPanZoomController.h"
|
||||
#include "mozilla/layers/CompositorChild.h"
|
||||
#include "mozilla/layers/PLayersChild.h"
|
||||
@ -38,7 +37,6 @@
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "nsIAppsService.h"
|
||||
#include "nsIBaseWindow.h"
|
||||
#include "nsICachedFileDescriptorListener.h"
|
||||
#include "nsIComponentManager.h"
|
||||
#include "nsIDocumentInlines.h"
|
||||
#include "nsIDOMClassInfo.h"
|
||||
@ -123,98 +121,6 @@ public:
|
||||
const InfallibleTArray<nsString>& aStringParams);
|
||||
};
|
||||
|
||||
class TabChild::CachedFileDescriptorInfo
|
||||
{
|
||||
struct PathOnlyComparatorHelper
|
||||
{
|
||||
bool Equals(const nsAutoPtr<CachedFileDescriptorInfo>& a,
|
||||
const CachedFileDescriptorInfo& b) const
|
||||
{
|
||||
return a->mPath == b.mPath;
|
||||
}
|
||||
};
|
||||
|
||||
struct PathAndCallbackComparatorHelper
|
||||
{
|
||||
bool Equals(const nsAutoPtr<CachedFileDescriptorInfo>& a,
|
||||
const CachedFileDescriptorInfo& b) const
|
||||
{
|
||||
return a->mPath == b.mPath &&
|
||||
a->mCallback == b.mCallback;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
nsString mPath;
|
||||
FileDescriptor mFileDescriptor;
|
||||
nsCOMPtr<nsICachedFileDescriptorListener> mCallback;
|
||||
bool mCanceled;
|
||||
|
||||
CachedFileDescriptorInfo(const nsAString& aPath)
|
||||
: mPath(aPath), mCanceled(false)
|
||||
{ }
|
||||
|
||||
CachedFileDescriptorInfo(const nsAString& aPath,
|
||||
const FileDescriptor& aFileDescriptor)
|
||||
: mPath(aPath), mFileDescriptor(aFileDescriptor), mCanceled(false)
|
||||
{ }
|
||||
|
||||
CachedFileDescriptorInfo(const nsAString& aPath,
|
||||
nsICachedFileDescriptorListener* aCallback)
|
||||
: mPath(aPath), mCallback(aCallback), mCanceled(false)
|
||||
{ }
|
||||
|
||||
PathOnlyComparatorHelper PathOnlyComparator() const
|
||||
{
|
||||
return PathOnlyComparatorHelper();
|
||||
}
|
||||
|
||||
PathAndCallbackComparatorHelper PathAndCallbackComparator() const
|
||||
{
|
||||
return PathAndCallbackComparatorHelper();
|
||||
}
|
||||
|
||||
void FireCallback() const
|
||||
{
|
||||
mCallback->OnCachedFileDescriptor(mPath, mFileDescriptor);
|
||||
}
|
||||
};
|
||||
|
||||
class TabChild::CachedFileDescriptorCallbackRunnable : public nsRunnable
|
||||
{
|
||||
typedef TabChild::CachedFileDescriptorInfo CachedFileDescriptorInfo;
|
||||
|
||||
nsAutoPtr<CachedFileDescriptorInfo> mInfo;
|
||||
|
||||
public:
|
||||
CachedFileDescriptorCallbackRunnable(CachedFileDescriptorInfo* aInfo)
|
||||
: mInfo(aInfo)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aInfo);
|
||||
MOZ_ASSERT(!aInfo->mPath.IsEmpty());
|
||||
MOZ_ASSERT(aInfo->mCallback);
|
||||
}
|
||||
|
||||
void Dispatch()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsresult rv = NS_DispatchToCurrentThread(this);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
}
|
||||
|
||||
private:
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mInfo);
|
||||
|
||||
mInfo->FireCallback();
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
StaticRefPtr<TabChild> sPreallocatedTab;
|
||||
|
||||
/*static*/ void
|
||||
@ -1211,130 +1117,6 @@ TabChild::RecvLoadURL(const nsCString& uri)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabChild::RecvCacheFileDescriptor(const nsString& aPath,
|
||||
const FileDescriptor& aFileDescriptor)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!aPath.IsEmpty());
|
||||
|
||||
// aFileDescriptor may be invalid here, but the callback will choose how to
|
||||
// handle it.
|
||||
|
||||
// First see if we already have a request for this path.
|
||||
const CachedFileDescriptorInfo search(aPath);
|
||||
uint32_t index =
|
||||
mCachedFileDescriptorInfos.IndexOf(search, 0,
|
||||
search.PathOnlyComparator());
|
||||
if (index == mCachedFileDescriptorInfos.NoIndex) {
|
||||
// We haven't had any requests for this path yet. Assume that we will
|
||||
// in a little while and save the file descriptor here.
|
||||
mCachedFileDescriptorInfos.AppendElement(
|
||||
new CachedFileDescriptorInfo(aPath, aFileDescriptor));
|
||||
return true;
|
||||
}
|
||||
|
||||
nsAutoPtr<CachedFileDescriptorInfo>& info =
|
||||
mCachedFileDescriptorInfos[index];
|
||||
|
||||
MOZ_ASSERT(info);
|
||||
MOZ_ASSERT(info->mPath == aPath);
|
||||
MOZ_ASSERT(!info->mFileDescriptor.IsValid());
|
||||
MOZ_ASSERT(info->mCallback);
|
||||
|
||||
// If this callback has been canceled then we can simply close the file
|
||||
// descriptor and forget about the callback.
|
||||
if (info->mCanceled) {
|
||||
// Only close if this is a valid file descriptor.
|
||||
if (aFileDescriptor.IsValid()) {
|
||||
nsRefPtr<CloseFileRunnable> runnable =
|
||||
new CloseFileRunnable(aFileDescriptor);
|
||||
runnable->Dispatch();
|
||||
}
|
||||
} else {
|
||||
// Not canceled so fire the callback.
|
||||
info->mFileDescriptor = aFileDescriptor;
|
||||
|
||||
// We don't need a runnable here because we should already be at the top
|
||||
// of the event loop. Just fire immediately.
|
||||
info->FireCallback();
|
||||
}
|
||||
|
||||
mCachedFileDescriptorInfos.RemoveElementAt(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabChild::GetCachedFileDescriptor(const nsAString& aPath,
|
||||
nsICachedFileDescriptorListener* aCallback)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!aPath.IsEmpty());
|
||||
MOZ_ASSERT(aCallback);
|
||||
|
||||
// First see if we've already received a cached file descriptor for this
|
||||
// path.
|
||||
const CachedFileDescriptorInfo search(aPath);
|
||||
uint32_t index =
|
||||
mCachedFileDescriptorInfos.IndexOf(search, 0,
|
||||
search.PathOnlyComparator());
|
||||
if (index == mCachedFileDescriptorInfos.NoIndex) {
|
||||
// We haven't received a file descriptor for this path yet. Assume that
|
||||
// we will in a little while and save the request here.
|
||||
mCachedFileDescriptorInfos.AppendElement(
|
||||
new CachedFileDescriptorInfo(aPath, aCallback));
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoPtr<CachedFileDescriptorInfo>& info =
|
||||
mCachedFileDescriptorInfos[index];
|
||||
|
||||
MOZ_ASSERT(info);
|
||||
MOZ_ASSERT(info->mPath == aPath);
|
||||
MOZ_ASSERT(!info->mCallback);
|
||||
MOZ_ASSERT(!info->mCanceled);
|
||||
|
||||
info->mCallback = aCallback;
|
||||
|
||||
nsRefPtr<CachedFileDescriptorCallbackRunnable> runnable =
|
||||
new CachedFileDescriptorCallbackRunnable(info.forget());
|
||||
runnable->Dispatch();
|
||||
|
||||
mCachedFileDescriptorInfos.RemoveElementAt(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
TabChild::CancelCachedFileDescriptorCallback(
|
||||
const nsAString& aPath,
|
||||
nsICachedFileDescriptorListener* aCallback)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!aPath.IsEmpty());
|
||||
MOZ_ASSERT(aCallback);
|
||||
|
||||
const CachedFileDescriptorInfo search(aPath, aCallback);
|
||||
uint32_t index =
|
||||
mCachedFileDescriptorInfos.IndexOf(search, 0,
|
||||
search.PathAndCallbackComparator());
|
||||
if (index == mCachedFileDescriptorInfos.NoIndex) {
|
||||
// Nothing to do here.
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoPtr<CachedFileDescriptorInfo>& info =
|
||||
mCachedFileDescriptorInfos[index];
|
||||
|
||||
MOZ_ASSERT(info);
|
||||
MOZ_ASSERT(info->mPath == aPath);
|
||||
MOZ_ASSERT(!info->mFileDescriptor.IsValid());
|
||||
MOZ_ASSERT(info->mCallback == aCallback);
|
||||
MOZ_ASSERT(!info->mCanceled);
|
||||
|
||||
// Set this flag so that we will close the file descriptor when it arrives.
|
||||
info->mCanceled = true;
|
||||
}
|
||||
|
||||
void
|
||||
TabChild::DoFakeShow()
|
||||
{
|
||||
|
@ -54,7 +54,6 @@
|
||||
#include "mozilla/dom/TabContext.h"
|
||||
|
||||
struct gfxMatrix;
|
||||
class nsICachedFileDescriptorListener;
|
||||
|
||||
namespace mozilla {
|
||||
namespace layout {
|
||||
@ -197,9 +196,6 @@ public:
|
||||
const mozilla::dom::StructuredCloneData& aData);
|
||||
|
||||
virtual bool RecvLoadURL(const nsCString& uri);
|
||||
virtual bool RecvCacheFileDescriptor(const nsString& aPath,
|
||||
const FileDescriptor& aFileDescriptor)
|
||||
MOZ_OVERRIDE;
|
||||
virtual bool RecvShow(const nsIntSize& size);
|
||||
virtual bool RecvUpdateDimensions(const nsRect& rect, const nsIntSize& size, const ScreenOrientation& orientation);
|
||||
virtual bool RecvUpdateFrame(const mozilla::layers::FrameMetrics& aFrameMetrics);
|
||||
@ -321,15 +317,6 @@ public:
|
||||
*/
|
||||
void GetAppType(nsAString& aAppType) const { aAppType = mAppType; }
|
||||
|
||||
// Returns true if the file descriptor was found in the cache, false
|
||||
// otherwise.
|
||||
bool GetCachedFileDescriptor(const nsAString& aPath,
|
||||
nsICachedFileDescriptorListener* aCallback);
|
||||
|
||||
void CancelCachedFileDescriptorCallback(
|
||||
const nsAString& aPath,
|
||||
nsICachedFileDescriptorListener* aCallback);
|
||||
|
||||
protected:
|
||||
virtual PRenderFrameChild* AllocPRenderFrame(ScrollingBehavior* aScrolling,
|
||||
LayersBackend* aBackend,
|
||||
@ -425,9 +412,6 @@ private:
|
||||
return utils;
|
||||
}
|
||||
|
||||
class CachedFileDescriptorInfo;
|
||||
class CachedFileDescriptorCallbackRunnable;
|
||||
|
||||
nsCOMPtr<nsIWebNavigation> mWebNav;
|
||||
nsCOMPtr<nsIWidget> mWidget;
|
||||
nsCOMPtr<nsIURI> mLastURI;
|
||||
@ -446,9 +430,6 @@ private:
|
||||
// the touch we're tracking. That is, if touchend or a touchmove
|
||||
// that exceeds the gesture threshold doesn't happen.
|
||||
CancelableTask* mTapHoldTimer;
|
||||
// At present only 1 of these is really expected.
|
||||
nsAutoTArray<nsAutoPtr<CachedFileDescriptorInfo>, 1>
|
||||
mCachedFileDescriptorInfos;
|
||||
float mOldViewportWidth;
|
||||
nscolor mLastBackgroundColor;
|
||||
ScrollingBehavior mScrolling;
|
||||
|
@ -50,7 +50,6 @@
|
||||
#include "nsSerializationHelper.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "private/pprio.h"
|
||||
#include "StructuredCloneUtils.h"
|
||||
#include "TabChild.h"
|
||||
#include <algorithm>
|
||||
@ -67,116 +66,6 @@ using namespace mozilla::dom::indexedDB;
|
||||
// from the ones registered by webProgressListeners.
|
||||
#define NOTIFY_FLAG_SHIFT 16
|
||||
|
||||
class OpenFileAndSendFDRunnable : public nsRunnable
|
||||
{
|
||||
const nsString mPath;
|
||||
nsRefPtr<TabParent> mTabParent;
|
||||
nsCOMPtr<nsIEventTarget> mEventTarget;
|
||||
PRFileDesc* mFD;
|
||||
|
||||
public:
|
||||
OpenFileAndSendFDRunnable(const nsAString& aPath, TabParent* aTabParent)
|
||||
: mPath(aPath), mTabParent(aTabParent), mFD(nullptr)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!aPath.IsEmpty());
|
||||
MOZ_ASSERT(aTabParent);
|
||||
}
|
||||
|
||||
void Dispatch()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
mEventTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
|
||||
NS_ENSURE_TRUE_VOID(mEventTarget);
|
||||
|
||||
nsresult rv = mEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
}
|
||||
|
||||
private:
|
||||
~OpenFileAndSendFDRunnable()
|
||||
{
|
||||
MOZ_ASSERT(!mFD);
|
||||
}
|
||||
|
||||
// This shouldn't be called directly except by the event loop. Use Dispatch
|
||||
// to start the sequence.
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
SendResponse();
|
||||
} else if (mFD) {
|
||||
CloseFile();
|
||||
} else {
|
||||
OpenFile();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void SendResponse()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mTabParent);
|
||||
MOZ_ASSERT(mEventTarget);
|
||||
MOZ_ASSERT(mFD);
|
||||
|
||||
nsRefPtr<TabParent> tabParent;
|
||||
mTabParent.swap(tabParent);
|
||||
|
||||
FileDescriptor::PlatformHandleType handle =
|
||||
FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(mFD));
|
||||
|
||||
mozilla::unused << tabParent->SendCacheFileDescriptor(mPath, handle);
|
||||
|
||||
nsCOMPtr<nsIEventTarget> eventTarget;
|
||||
mEventTarget.swap(eventTarget);
|
||||
|
||||
if (NS_FAILED(eventTarget->Dispatch(this, NS_DISPATCH_NORMAL))) {
|
||||
NS_WARNING("Failed to dispatch to stream transport service!");
|
||||
|
||||
// It's probably safer to take the main thread IO hit here rather
|
||||
// than leak a file descriptor.
|
||||
CloseFile();
|
||||
}
|
||||
}
|
||||
|
||||
void OpenFile()
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
MOZ_ASSERT(!mFD);
|
||||
|
||||
nsCOMPtr<nsIFile> file;
|
||||
nsresult rv = NS_NewLocalFile(mPath, false, getter_AddRefs(file));
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
PRFileDesc* fd;
|
||||
rv = file->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
mFD = fd;
|
||||
|
||||
if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
|
||||
NS_WARNING("Failed to dispatch to main thread!");
|
||||
|
||||
CloseFile();
|
||||
}
|
||||
}
|
||||
|
||||
void CloseFile()
|
||||
{
|
||||
// It's possible for this to happen on the main thread if the dispatch
|
||||
// to the stream service fails after we've already opened the file so
|
||||
// we can't assert the thread we're running on.
|
||||
|
||||
MOZ_ASSERT(mFD);
|
||||
|
||||
PR_Close(mFD);
|
||||
mFD = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
@ -204,7 +93,6 @@ TabParent::TabParent(const TabContext& aContext)
|
||||
, mUpdatedDimensions(false)
|
||||
, mMarkedDestroying(false)
|
||||
, mIsDestroyed(false)
|
||||
, mAppPackageFileDescriptorSent(false)
|
||||
{
|
||||
}
|
||||
|
||||
@ -342,66 +230,23 @@ TabParent::AnswerCreateWindow(PBrowserParent** retval)
|
||||
void
|
||||
TabParent::LoadURL(nsIURI* aURI)
|
||||
{
|
||||
MOZ_ASSERT(aURI);
|
||||
|
||||
if (mIsDestroyed) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
if (!mShown) {
|
||||
nsAutoCString spec;
|
||||
if (aURI) {
|
||||
aURI->GetSpec(spec);
|
||||
}
|
||||
NS_WARNING(nsPrintfCString("TabParent::LoadURL(%s) called before "
|
||||
"Show(). Ignoring LoadURL.\n", spec.get()).get());
|
||||
return;
|
||||
}
|
||||
|
||||
nsCString spec;
|
||||
aURI->GetSpec(spec);
|
||||
|
||||
if (!mShown) {
|
||||
NS_WARNING(nsPrintfCString("TabParent::LoadURL(%s) called before "
|
||||
"Show(). Ignoring LoadURL.\n",
|
||||
spec.get()).get());
|
||||
return;
|
||||
}
|
||||
|
||||
unused << SendLoadURL(spec);
|
||||
|
||||
// If this app is a packaged app then we can speed startup by sending over
|
||||
// the file descriptor for the "application.zip" file that it will
|
||||
// invariably request. Only do this once.
|
||||
if (!mAppPackageFileDescriptorSent) {
|
||||
mAppPackageFileDescriptorSent = true;
|
||||
|
||||
nsCOMPtr<mozIApplication> app = GetOwnOrContainingApp();
|
||||
if (app) {
|
||||
nsString manifestURL;
|
||||
nsresult rv = app->GetManifestURL(manifestURL);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
if (StringBeginsWith(manifestURL, NS_LITERAL_STRING("app:"))) {
|
||||
nsString basePath;
|
||||
rv = app->GetBasePath(basePath);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
nsString appId;
|
||||
rv = app->GetId(appId);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
nsCOMPtr<nsIFile> packageFile;
|
||||
rv = NS_NewLocalFile(basePath, false,
|
||||
getter_AddRefs(packageFile));
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
rv = packageFile->Append(appId);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
rv = packageFile->Append(NS_LITERAL_STRING("application.zip"));
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
nsString path;
|
||||
rv = packageFile->GetPath(path);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
|
||||
nsRefPtr<OpenFileAndSendFDRunnable> openFileRunnable =
|
||||
new OpenFileAndSendFDRunnable(path, this);
|
||||
openFileRunnable->Dispatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -304,8 +304,6 @@ private:
|
||||
// When true, the TabParent is invalid and we should not send IPC messages
|
||||
// anymore.
|
||||
bool mIsDestroyed;
|
||||
// Whether we have already sent a FileDescriptor for the app package.
|
||||
bool mAppPackageFileDescriptorSent;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
@ -1,38 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_dom_ipc_nsICachedFileDescriptorListener_h
|
||||
#define mozilla_dom_ipc_nsICachedFileDescriptorListener_h
|
||||
|
||||
#include "nsISupports.h"
|
||||
|
||||
#ifndef NS_NO_VTABLE
|
||||
#define NS_NO_VTABLE
|
||||
#endif
|
||||
|
||||
#define NS_ICACHEDFILEDESCRIPTORLISTENER_IID \
|
||||
{0x2cedaee0, 0x6ef2, 0x4f60, {0x9a, 0x6c, 0xdf, 0x4e, 0x4d, 0x65, 0x6a, 0xf7}}
|
||||
|
||||
class nsAString;
|
||||
|
||||
namespace mozilla {
|
||||
namespace ipc {
|
||||
class FileDescriptor;
|
||||
}
|
||||
}
|
||||
|
||||
class NS_NO_VTABLE nsICachedFileDescriptorListener : public nsISupports
|
||||
{
|
||||
public:
|
||||
NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICACHEDFILEDESCRIPTORLISTENER_IID)
|
||||
|
||||
virtual void
|
||||
OnCachedFileDescriptor(const nsAString& aPath,
|
||||
const mozilla::ipc::FileDescriptor& aFD) = 0;
|
||||
};
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(nsICachedFileDescriptorListener,
|
||||
NS_ICACHEDFILEDESCRIPTORLISTENER_IID)
|
||||
|
||||
#endif // mozilla_dom_ipc_nsICachedFileDescriptorListener_h
|
@ -15,6 +15,16 @@ MOCHITEST_FILES = \
|
||||
test_getUserMedia_basicAudio.html \
|
||||
test_getUserMedia_basicVideo.html \
|
||||
test_getUserMedia_basicVideoAudio.html \
|
||||
test_getUserMedia_gumWithinGum.html \
|
||||
test_getUserMedia_playAudioTwice.html \
|
||||
test_getUserMedia_playVideoTwice.html \
|
||||
test_getUserMedia_playVideoAudioTwice.html \
|
||||
test_getUserMedia_stopAudioStream.html \
|
||||
test_getUserMedia_stopAudioStreamWithFollowupAudio.html \
|
||||
test_getUserMedia_stopVideoStreamWithFollowupVideo.html \
|
||||
test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html \
|
||||
test_getUserMedia_stopVideoStream.html \
|
||||
test_getUserMedia_stopVideoAudioStream.html \
|
||||
test_peerConnection_basicAudio.html \
|
||||
test_peerConnection_basicAudioVideo.html \
|
||||
test_peerConnection_basicAudioVideoCombined.html \
|
||||
|
@ -6,6 +6,9 @@ var Cc = SpecialPowers.Cc;
|
||||
var Ci = SpecialPowers.Ci;
|
||||
var Cr = SpecialPowers.Cr;
|
||||
|
||||
// Specifies whether we are using fake streams to run this automation
|
||||
var FAKE_ENABLED = true;
|
||||
|
||||
/**
|
||||
* Setup any Mochitest for WebRTC by enabling the preference for
|
||||
* peer connections. As by bug 797979 it will also enable mozGetUserMedia().
|
||||
@ -24,7 +27,9 @@ function runTest(aCallback, desktopSupportedOnly) {
|
||||
ok(true, navigator.userAgent + ' currently not supported');
|
||||
SimpleTest.finish();
|
||||
} else {
|
||||
SpecialPowers.pushPrefEnv({'set': [['media.peerconnection.enabled', true]]}, function () {
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['media.peerconnection.enabled', true],
|
||||
['media.navigator.permission.denied', true]]}, function () {
|
||||
try {
|
||||
aCallback();
|
||||
}
|
||||
@ -35,6 +40,22 @@ function runTest(aCallback, desktopSupportedOnly) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper function for mozGetUserMedia to allow a singular area of control
|
||||
* for determining whether we run this with fake devices or not.
|
||||
*
|
||||
* @param {Dictionary} constraints the constraints for this mozGetUserMedia
|
||||
* callback
|
||||
* @param {Function} onSuccess the success callback if the stream is
|
||||
* successfully retrieved
|
||||
* @param {Function} onError the error callback if the stream fails to be
|
||||
* retrieved
|
||||
*/
|
||||
function getUserMedia(constraints, onSuccess, onError) {
|
||||
constraints["fake"] = FAKE_ENABLED;
|
||||
navigator.mozGetUserMedia(constraints, onSuccess, onError);
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback function fired only under unexpected circumstances while
|
||||
* running the tests. Kills off the test as well gracefully.
|
||||
|
@ -2,42 +2,63 @@
|
||||
* 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/. */
|
||||
|
||||
var TIMEOUT_LENGTH = 10000;
|
||||
|
||||
/**
|
||||
* This class manages playback of a HTMLMediaElement with a MediaStream.
|
||||
* When constructed by a caller, an object instance is created with
|
||||
* a media element and a media stream object.
|
||||
*
|
||||
* @param {HTMLMediaElement} mediaElement the media element for playback
|
||||
* @param {LocalMediaStream} mediaStream the media stream used in
|
||||
* the mediaElement for playback
|
||||
* @param {MediaStream} mediaStream the media stream used in
|
||||
* the mediaElement for playback
|
||||
*/
|
||||
function MediaStreamPlayback(mediaElement, mediaStream) {
|
||||
|
||||
/** The HTMLMediaElement used for media playback */
|
||||
this.mediaElement = mediaElement;
|
||||
|
||||
/** The LocalMediaStream used with the HTMLMediaElement */
|
||||
this.mediaStream = mediaStream;
|
||||
}
|
||||
|
||||
MediaStreamPlayback.prototype = {
|
||||
|
||||
/**
|
||||
* Starts media with a media stream, runs it until a canplaythrough and
|
||||
* timeupdate event fires, and stops the media.
|
||||
*
|
||||
* @param {Boolean} isResume specifies if this media element is being resumed
|
||||
* from a previous run
|
||||
* @param {Function} onSuccess the success callback if the media playback
|
||||
* start and stop cycle completes successfully
|
||||
* @param {Function} onError the error callback if the media playback
|
||||
* start and stop cycle fails
|
||||
*/
|
||||
playMedia : function MSP_playMedia(isResume, onSuccess, onError) {
|
||||
var self = this;
|
||||
|
||||
this.startMedia(isResume, function() {
|
||||
self.stopMediaElement();
|
||||
onSuccess();
|
||||
}, onError);
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts the media with the associated stream.
|
||||
*
|
||||
* @param {Integer} timeoutLength the timeout length to wait for
|
||||
* canplaythrough to fire in milliseconds
|
||||
* @param {Boolean} isResume specifies if the media element playback
|
||||
* is being resumed from a previous run
|
||||
* @param {Function} onSuccess the success function call back
|
||||
* if media starts correctly
|
||||
* @param {Function} onError the error function call back
|
||||
* if media fails to start
|
||||
*/
|
||||
this.startMedia = function(timeoutLength, onSuccess, onError) {
|
||||
startMedia : function MSP_startMedia(isResume, onSuccess, onError) {
|
||||
var self = this;
|
||||
var canPlayThroughFired = false;
|
||||
|
||||
// Verifies we've received a correctly initialized LocalMediaStream
|
||||
ok(this.mediaStream instanceof LocalMediaStream,
|
||||
"Stream should be a LocalMediaStream");
|
||||
is(this.mediaStream.currentTime, 0,
|
||||
"Before starting the media element, currentTime = 0");
|
||||
// If we're initially running this media, check that the time is zero
|
||||
if (!isResume) {
|
||||
is(this.mediaStream.currentTime, 0,
|
||||
"Before starting the media element, currentTime = 0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback fired when the canplaythrough event is fired. We only
|
||||
@ -81,11 +102,11 @@ function MediaStreamPlayback(mediaElement, mediaStream) {
|
||||
var timeUpdateFired = false;
|
||||
|
||||
var timeUpdateCallback = function() {
|
||||
if(self.mediaStream.currentTime > 0 &&
|
||||
self.mediaElement.currentTime > 0) {
|
||||
if (self.mediaStream.currentTime > 0 &&
|
||||
self.mediaElement.currentTime > 0) {
|
||||
timeUpdateFired = true;
|
||||
self.mediaElement.removeEventListener('timeupdate', timeUpdateCallback,
|
||||
false);
|
||||
self.mediaElement.removeEventListener('timeupdate',
|
||||
timeUpdateCallback, false);
|
||||
onSuccess();
|
||||
}
|
||||
};
|
||||
@ -97,13 +118,13 @@ function MediaStreamPlayback(mediaElement, mediaStream) {
|
||||
|
||||
// If timeupdate doesn't fire in enough time, we fail the test
|
||||
setTimeout(function() {
|
||||
if(!timeUpdateFired) {
|
||||
if (!timeUpdateFired) {
|
||||
self.mediaElement.removeEventListener('timeupdate',
|
||||
timeUpdateCallback, false);
|
||||
ok(false, "timeUpdate event never fired");
|
||||
onError();
|
||||
}
|
||||
}, timeoutLength);
|
||||
}, TIMEOUT_LENGTH);
|
||||
};
|
||||
|
||||
// Adds a listener intended to be fired when playback is available
|
||||
@ -112,19 +133,19 @@ function MediaStreamPlayback(mediaElement, mediaStream) {
|
||||
false);
|
||||
|
||||
// Hooks up the media stream to the media element and starts playing it
|
||||
this.mediaElement.mozSrcObject = mediaStream;
|
||||
this.mediaElement.mozSrcObject = this.mediaStream;
|
||||
this.mediaElement.play();
|
||||
|
||||
// If canplaythrough doesn't fire in enough time, we fail the test
|
||||
setTimeout(function() {
|
||||
if(!canPlayThroughFired) {
|
||||
if (!canPlayThroughFired) {
|
||||
self.mediaElement.removeEventListener('canplaythrough',
|
||||
canPlayThroughCallback, false);
|
||||
ok(false, "canplaythrough event never fired");
|
||||
onError();
|
||||
}
|
||||
}, timeoutLength);
|
||||
};
|
||||
}, TIMEOUT_LENGTH);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops the media with the associated stream.
|
||||
@ -132,28 +153,93 @@ function MediaStreamPlayback(mediaElement, mediaStream) {
|
||||
* Precondition: The media stream and element should both be actively
|
||||
* being played.
|
||||
*/
|
||||
this.stopMedia = function() {
|
||||
stopMediaElement : function MSP_stopMediaElement() {
|
||||
this.mediaElement.pause();
|
||||
this.mediaElement.mozSrcObject = null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts media with a media stream, runs it until a canplaythrough and
|
||||
* timeupdate event fires, and stops the media.
|
||||
*
|
||||
* @param {Integer} timeoutLength the length of time to wait for certain
|
||||
* media events to fire
|
||||
* @param {Function} onSuccess the success callback if the media playback
|
||||
* start and stop cycle completes successfully
|
||||
* @param {Function} onError the error callback if the media playback
|
||||
* start and stop cycle fails
|
||||
*/
|
||||
this.playMedia = function(timeoutLength, onSuccess, onError) {
|
||||
var self = this;
|
||||
|
||||
this.startMedia(timeoutLength, function() {
|
||||
self.stopMedia();
|
||||
/**
|
||||
* This class is basically the same as MediaStreamPlayback except
|
||||
* ensures that the instance provided startMedia is a MediaStream.
|
||||
*
|
||||
* @param {HTMLMediaElement} mediaElement the media element for playback
|
||||
* @param {LocalMediaStream} mediaStream the media stream used in
|
||||
* the mediaElement for playback
|
||||
*/
|
||||
function LocalMediaStreamPlayback(mediaElement, mediaStream) {
|
||||
ok(mediaStream instanceof LocalMediaStream,
|
||||
"Stream should be a LocalMediaStream");
|
||||
MediaStreamPlayback.call(this, mediaElement, mediaStream);
|
||||
}
|
||||
|
||||
// Sets up the inheritance chain from LMSP --> MSP
|
||||
LocalMediaStreamPlayback.prototype = new MediaStreamPlayback();
|
||||
LocalMediaStreamPlayback.prototype.constructor = LocalMediaStreamPlayback;
|
||||
|
||||
/**
|
||||
* Starts media with a media stream, runs it until a canplaythrough and
|
||||
* timeupdate event fires, and calls stop() on the stream.
|
||||
*
|
||||
* @param {Boolean} isResume specifies if this media element is being resumed
|
||||
* from a previous run
|
||||
* @param {Function} onSuccess the success callback if the media element
|
||||
* successfully fires ended on a stop() call
|
||||
* on the stream
|
||||
* @param {Function} onError the error callback if the media element fails
|
||||
* to fire an ended callback on a stop() call
|
||||
* on the stream
|
||||
*/
|
||||
LocalMediaStreamPlayback.prototype.playMediaWithStreamStop = function(
|
||||
isResume, onSuccess, onError) {
|
||||
var self = this;
|
||||
|
||||
this.startMedia(isResume, function() {
|
||||
self.stopStreamInMediaPlayback(function() {
|
||||
self.stopMediaElement();
|
||||
onSuccess();
|
||||
}, onError);
|
||||
};
|
||||
}, onError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the local media stream while it's currently in playback in
|
||||
* a media element.
|
||||
*
|
||||
* Precondition: The media stream and element should both be actively
|
||||
* being played.
|
||||
*
|
||||
* @param {Function} onSuccess the success callback if the media element
|
||||
* fires an ended event from stop() being called
|
||||
* @param {Function} onError the error callback if the media element
|
||||
* fails to fire an ended event from stop() being
|
||||
* called
|
||||
*/
|
||||
LocalMediaStreamPlayback.prototype.stopStreamInMediaPlayback = function(
|
||||
onSuccess, onError) {
|
||||
var endedFired = false;
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* Callback fired when the ended event fires when stop() is called on the
|
||||
* stream.
|
||||
*/
|
||||
var endedCallback = function() {
|
||||
endedFired = true;
|
||||
self.mediaElement.removeEventListener('ended', endedCallback, false);
|
||||
ok(true, "ended event successfully fired");
|
||||
onSuccess();
|
||||
};
|
||||
|
||||
this.mediaElement.addEventListener('ended', endedCallback, false);
|
||||
this.mediaStream.stop();
|
||||
|
||||
// If ended doesn't fire in enough time, then we fail the test
|
||||
setTimeout(function() {
|
||||
if (!endedFired) {
|
||||
ok(false, "ended event never fired");
|
||||
onError();
|
||||
}
|
||||
}, TIMEOUT_LENGTH);
|
||||
}
|
||||
|
@ -27,13 +27,16 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=781534
|
||||
runTest(function () {
|
||||
var testAudio = document.getElementById('testAudio');
|
||||
|
||||
navigator.mozGetUserMedia({audio: true, fake: true}, function (aStream) {
|
||||
var playback = new MediaStreamPlayback(testAudio, aStream);
|
||||
playback.playMedia(10000, function () {
|
||||
getUserMedia({audio: true}, function (aStream) {
|
||||
var playback = new LocalMediaStreamPlayback(testAudio, aStream);
|
||||
|
||||
playback.playMedia(false, function () {
|
||||
aStream.stop();
|
||||
SimpleTest.finish();
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, true);
|
||||
|
||||
</script>
|
||||
|
@ -27,13 +27,16 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=781534
|
||||
runTest(function () {
|
||||
var testVideo = document.getElementById('testVideo');
|
||||
|
||||
navigator.mozGetUserMedia({video: true, fake: true}, function (aStream) {
|
||||
var playback = new MediaStreamPlayback(testVideo, aStream);
|
||||
playback.playMedia(10000, function () {
|
||||
getUserMedia({video: true}, function (aStream) {
|
||||
var playback = new LocalMediaStreamPlayback(testVideo, aStream);
|
||||
|
||||
playback.playMedia(false, function () {
|
||||
aStream.stop();
|
||||
SimpleTest.finish();
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, true);
|
||||
|
||||
</script>
|
||||
|
@ -27,13 +27,14 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=781534
|
||||
runTest(function () {
|
||||
var testVideoAudio = document.getElementById('testVideoAudio');
|
||||
|
||||
navigator.mozGetUserMedia({video: true, audio: true, fake: true},
|
||||
function (aStream) {
|
||||
var playback = new MediaStreamPlayback(testVideoAudio, aStream);
|
||||
playback.playMedia(10000, function () {
|
||||
aStream.stop();
|
||||
SimpleTest.finish();
|
||||
}, unexpectedCallbackAndFinish);
|
||||
getUserMedia({video: true, audio: true}, function (aStream) {
|
||||
var playback = new LocalMediaStreamPlayback(testVideoAudio, aStream);
|
||||
|
||||
playback.playMedia(false, function () {
|
||||
aStream.stop();
|
||||
SimpleTest.finish();
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
}, true);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<!DOCTYPE HTML>
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=795367
|
||||
|
@ -0,0 +1,57 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=822109
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>mozGetUserMedia gum within gum</title>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=822109">mozGetUserMedia gum within gum</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
<video id="testVideo"></video>
|
||||
<audio id="testAudio"></audio>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/**
|
||||
* Run a test that we can complete a playback cycle for a video,
|
||||
* then upon completion, do a playback cycle with audio, such that
|
||||
* the audio gum call happens within the video gum call.
|
||||
*/
|
||||
runTest(function () {
|
||||
getUserMedia({video: true}, function(videoStream) {
|
||||
var testVideo = document.getElementById('testVideo');
|
||||
var videoStreamPlayback = new LocalMediaStreamPlayback(testVideo,
|
||||
videoStream);
|
||||
|
||||
videoStreamPlayback.playMedia(false, function() {
|
||||
getUserMedia({audio: true}, function(audioStream) {
|
||||
var testAudio = document.getElementById('testAudio');
|
||||
var audioStreamPlayback = new LocalMediaStreamPlayback(testAudio,
|
||||
audioStream);
|
||||
|
||||
audioStreamPlayback.playMedia(false, function() {
|
||||
audioStream.stop();
|
||||
videoStream.stop();
|
||||
SimpleTest.finish();
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
}, true);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,47 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=822109
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>mozGetUserMedia Play Audio Twice</title>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=822109">mozGetUserMedia Play Audio Twice</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
<audio id="testAudio"></audio>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/**
|
||||
* Run a test that we can complete an audio playback cycle twice in a row.
|
||||
*/
|
||||
runTest(function () {
|
||||
getUserMedia({audio: true}, function(audioStream) {
|
||||
var testAudio = document.getElementById('testAudio');
|
||||
var audioStreamPlayback = new LocalMediaStreamPlayback(testAudio,
|
||||
audioStream);
|
||||
|
||||
audioStreamPlayback.playMedia(false, function() {
|
||||
|
||||
audioStreamPlayback.playMedia(true, function() {
|
||||
audioStream.stop();
|
||||
SimpleTest.finish();
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
}, true);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,46 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=822109
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>mozGetUserMedia Play Video and Audio Twice</title>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=822109">mozGetUserMedia Play Video and Audio Twice</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
<video id="testVideo"></video>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/**
|
||||
* Run a test that we can complete a video playback cycle twice in a row.
|
||||
*/
|
||||
runTest(function () {
|
||||
getUserMedia({video: true, audio: true}, function(stream) {
|
||||
var testVideo = document.getElementById('testVideo');
|
||||
var streamPlayback = new LocalMediaStreamPlayback(testVideo, stream);
|
||||
|
||||
streamPlayback.playMedia(false, function() {
|
||||
|
||||
streamPlayback.playMedia(true, function() {
|
||||
stream.stop();
|
||||
SimpleTest.finish();
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
}, true);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,47 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=822109
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>mozGetUserMedia Play Video Twice</title>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=822109">mozGetUserMedia Play Video Twice</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
<video id="testVideo"></video>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/**
|
||||
* Run a test that we can complete a video playback cycle twice in a row.
|
||||
*/
|
||||
runTest(function () {
|
||||
getUserMedia({video: true}, function(videoStream) {
|
||||
var testVideo = document.getElementById('testVideo');
|
||||
var videoStreamPlayback = new LocalMediaStreamPlayback(testVideo,
|
||||
videoStream);
|
||||
|
||||
videoStreamPlayback.playMedia(false, function() {
|
||||
|
||||
videoStreamPlayback.playMedia(true, function() {
|
||||
videoStream.stop();
|
||||
SimpleTest.finish();
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
}, true);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,40 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=822109
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>mozGetUserMedia Stop Audio Stream</title>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=822109">mozGetUserMedia Stop Audio Stream</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
<audio id="testAudio"></video>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/**
|
||||
* Run a test to verify that we can start an audio stream in a media element,
|
||||
* call stop() on the stream, and successfully get an ended event fired.
|
||||
*/
|
||||
runTest(function () {
|
||||
getUserMedia({audio: true}, function(stream) {
|
||||
var testAudio = document.getElementById('testAudio');
|
||||
var audioStreamPlayback = new LocalMediaStreamPlayback(testAudio, stream);
|
||||
|
||||
audioStreamPlayback.playMediaWithStreamStop(false, SimpleTest.finish,
|
||||
unexpectedCallbackAndFinish);
|
||||
}, unexpectedCallbackAndFinish);
|
||||
}, true);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,52 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=822109
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>mozGetUserMedia Stop Audio Stream With Followup Audio</title>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=822109">mozGetUserMedia Stop Audio Stream With Followup Audio</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
<audio id="testAudio"></audio>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/**
|
||||
* Run a test to verify that I can complete an audio gum playback in a media
|
||||
* element, stop the stream, and then complete another audio gum playback
|
||||
* in a media element.
|
||||
*/
|
||||
runTest(function () {
|
||||
getUserMedia({audio: true}, function(firstStream) {
|
||||
var testAudio = document.getElementById('testAudio');
|
||||
var streamPlayback = new LocalMediaStreamPlayback(testAudio, firstStream);
|
||||
|
||||
streamPlayback.playMediaWithStreamStop(false, function() {
|
||||
getUserMedia({audio: true}, function(secondStream) {
|
||||
streamPlayback.mediaStream = secondStream;
|
||||
|
||||
streamPlayback.playMedia(false, function() {
|
||||
secondStream.stop();
|
||||
SimpleTest.finish();
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
}, true);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,41 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=822109
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>mozGetUserMedia Stop Video Audio Stream</title>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=822109">mozGetUserMedia Stop Video Audio Stream</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
<video id="testVideo"></video>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/**
|
||||
* Run a test to verify that we can start a video+audio stream in a
|
||||
* media element, call stop() on the stream, and successfully get an
|
||||
* ended event fired.
|
||||
*/
|
||||
runTest(function () {
|
||||
getUserMedia({video: true, audio: true}, function(stream) {
|
||||
var testVideo = document.getElementById('testVideo');
|
||||
var streamPlayback = new LocalMediaStreamPlayback(testVideo, stream);
|
||||
|
||||
streamPlayback.playMediaWithStreamStop(false, SimpleTest.finish,
|
||||
unexpectedCallbackAndFinish);
|
||||
}, unexpectedCallbackAndFinish);
|
||||
}, true);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,52 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=822109
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>mozGetUserMedia Stop Video+Audio Stream With Followup Video+Audio</title>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=822109">mozGetUserMedia Stop Video+Audio Stream With Followup Video+Audio</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
<video id="testVideo"></video>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/**
|
||||
* Run a test to verify that I can complete an video+audio gum playback in a
|
||||
* media element, stop the stream, and then complete another video+audio gum
|
||||
* playback in a media element.
|
||||
*/
|
||||
runTest(function () {
|
||||
getUserMedia({video: true, audio: true}, function(firstStream) {
|
||||
var testVideo = document.getElementById('testVideo');
|
||||
var streamPlayback = new LocalMediaStreamPlayback(testVideo, firstStream);
|
||||
|
||||
streamPlayback.playMediaWithStreamStop(false, function() {
|
||||
getUserMedia({video: true, audio: true}, function(secondStream) {
|
||||
streamPlayback.mediaStream = secondStream;
|
||||
|
||||
streamPlayback.playMedia(false, function() {
|
||||
secondStream.stop();
|
||||
SimpleTest.finish();
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
}, true);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,40 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=822109
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>mozGetUserMedia Stop Video Stream</title>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=822109">mozGetUserMedia Stop Video Stream</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
<video id="testVideo"></video>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/**
|
||||
* Run a test to verify that we can start a video stream in a media element,
|
||||
* call stop() on the stream, and successfully get an ended event fired.
|
||||
*/
|
||||
runTest(function () {
|
||||
getUserMedia({video: true}, function(stream) {
|
||||
var testVideo = document.getElementById('testVideo');
|
||||
var videoStreamPlayback = new LocalMediaStreamPlayback(testVideo, stream);
|
||||
|
||||
videoStreamPlayback.playMediaWithStreamStop(false, SimpleTest.finish,
|
||||
unexpectedCallbackAndFinish);
|
||||
}, unexpectedCallbackAndFinish);
|
||||
}, true);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,53 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=822109
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>mozGetUserMedia Stop Video Stream With Followup Video</title>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=822109">mozGetUserMedia Stop Video Stream With Followup Video</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
<video id="testVideo"></video>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
|
||||
/**
|
||||
* Run a test to verify that I can complete an audio gum playback in a media
|
||||
* element, stop the stream, and then complete another audio gum playback
|
||||
* in a media element.
|
||||
*/
|
||||
runTest(function () {
|
||||
getUserMedia({video: true}, function(firstStream) {
|
||||
var testVideo = document.getElementById('testVideo');
|
||||
var streamPlayback = new LocalMediaStreamPlayback(testVideo,
|
||||
firstStream);
|
||||
|
||||
streamPlayback.playMediaWithStreamStop(false, function() {
|
||||
getUserMedia({video: true}, function(secondStream) {
|
||||
streamPlayback.mediaStream = secondStream;
|
||||
|
||||
streamPlayback.playMedia(false, function() {
|
||||
secondStream.stop();
|
||||
SimpleTest.finish();
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
|
||||
}, unexpectedCallbackAndFinish);
|
||||
}, true);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -30,7 +30,6 @@
|
||||
#include "nsIOutputStream.h"
|
||||
#include "nsIURL.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsXPIDLString.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsIProtocolProxyService2.h"
|
||||
#include "nsIStreamConverterService.h"
|
||||
@ -68,9 +67,8 @@
|
||||
#include "nsIXULRuntime.h"
|
||||
|
||||
// for the dialog
|
||||
#include "nsIStringBundle.h"
|
||||
#include "nsIWindowWatcher.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "nsIDOMWindow.h"
|
||||
|
||||
#include "nsIScriptGlobalObject.h"
|
||||
#include "nsIScriptGlobalObjectOwner.h"
|
||||
@ -200,20 +198,12 @@ PRLogModuleInfo* nsPluginLogging::gNPPLog = nullptr;
|
||||
PRLogModuleInfo* nsPluginLogging::gPluginLog = nullptr;
|
||||
#endif
|
||||
|
||||
#define BRAND_PROPERTIES_URL "chrome://branding/locale/brand.properties"
|
||||
#define PLUGIN_PROPERTIES_URL "chrome://global/locale/downloadProgress.properties"
|
||||
|
||||
// #defines for plugin cache and prefs
|
||||
#define NS_PREF_MAX_NUM_CACHED_INSTANCES "browser.plugins.max_num_cached_plugins"
|
||||
// Raise this from '10' to '50' to work around a bug in Apple's current Java
|
||||
// plugins on OS X Lion and SnowLeopard. See bug 705931.
|
||||
#define DEFAULT_NUMBER_OF_STOPPED_INSTANCES 50
|
||||
|
||||
#ifdef CALL_SAFETY_ON
|
||||
// By default we run OOPP, so we don't want to cover up crashes.
|
||||
bool gSkipPluginSafeCalls = true;
|
||||
#endif
|
||||
|
||||
nsIFile *nsPluginHost::sPluginTempDir;
|
||||
nsPluginHost *nsPluginHost::sInst;
|
||||
|
||||
@ -2180,11 +2170,6 @@ nsresult nsPluginHost::FindPlugins(bool aCreatePluginList, bool * aPluginsChange
|
||||
{
|
||||
Telemetry::AutoTimer<Telemetry::FIND_PLUGINS> telemetry;
|
||||
|
||||
#ifdef CALL_SAFETY_ON
|
||||
// check preferences on whether or not we want to try safe calls to plugins
|
||||
NS_INIT_PLUGIN_SAFE_CALLS;
|
||||
#endif
|
||||
|
||||
NS_ENSURE_ARG_POINTER(aPluginsChanged);
|
||||
|
||||
*aPluginsChanged = false;
|
||||
@ -3256,98 +3241,6 @@ NS_IMETHODIMP nsPluginHost::Observe(nsISupports *aSubject,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsPluginHost::HandleBadPlugin(PRLibrary* aLibrary, nsNPAPIPluginInstance *aInstance)
|
||||
{
|
||||
// the |aLibrary| parameter is not needed anymore, after we added |aInstance| which
|
||||
// can also be used to look up the plugin name, but we cannot get rid of it because
|
||||
// the |nsIPluginHost| interface is deprecated which in fact means 'frozen'
|
||||
|
||||
NS_ERROR("Plugin performed illegal operation");
|
||||
NS_ENSURE_ARG_POINTER(aInstance);
|
||||
|
||||
if (mDontShowBadPluginMessage)
|
||||
return NS_OK;
|
||||
|
||||
nsRefPtr<nsPluginInstanceOwner> owner = aInstance->GetOwner();
|
||||
|
||||
nsCOMPtr<nsIPrompt> prompt;
|
||||
GetPrompt(owner, getter_AddRefs(prompt));
|
||||
if (!prompt)
|
||||
return NS_OK;
|
||||
|
||||
nsCOMPtr<nsIStringBundleService> strings =
|
||||
mozilla::services::GetStringBundleService();
|
||||
if (!strings)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
nsCOMPtr<nsIStringBundle> bundle;
|
||||
nsresult rv = strings->CreateBundle(BRAND_PROPERTIES_URL, getter_AddRefs(bundle));
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
nsXPIDLString brandName;
|
||||
rv = bundle->GetStringFromName(NS_LITERAL_STRING("brandShortName").get(),
|
||||
getter_Copies(brandName));
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
rv = strings->CreateBundle(PLUGIN_PROPERTIES_URL, getter_AddRefs(bundle));
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
nsXPIDLString title, message, checkboxMessage;
|
||||
rv = bundle->GetStringFromName(NS_LITERAL_STRING("BadPluginTitle").get(),
|
||||
getter_Copies(title));
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
const PRUnichar *formatStrings[] = { brandName.get() };
|
||||
if (NS_FAILED(rv = bundle->FormatStringFromName(NS_LITERAL_STRING("BadPluginMessage").get(),
|
||||
formatStrings, 1, getter_Copies(message))))
|
||||
return rv;
|
||||
|
||||
rv = bundle->GetStringFromName(NS_LITERAL_STRING("BadPluginCheckboxMessage").get(),
|
||||
getter_Copies(checkboxMessage));
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
nsNPAPIPlugin *plugin = aInstance->GetPlugin();
|
||||
if (!plugin)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
nsPluginTag *pluginTag = TagForPlugin(plugin);
|
||||
|
||||
// add plugin name to the message
|
||||
nsCString pluginname;
|
||||
if (pluginTag) {
|
||||
if (!pluginTag->mName.IsEmpty()) {
|
||||
pluginname = pluginTag->mName;
|
||||
} else {
|
||||
pluginname = pluginTag->mFileName;
|
||||
}
|
||||
} else {
|
||||
pluginname.AppendLiteral("???");
|
||||
}
|
||||
|
||||
NS_ConvertUTF8toUTF16 msg(pluginname);
|
||||
msg.AppendLiteral("\n\n");
|
||||
msg.Append(message);
|
||||
|
||||
int32_t buttonPressed;
|
||||
bool checkboxState = false;
|
||||
rv = prompt->ConfirmEx(title, msg.get(),
|
||||
nsIPrompt::BUTTON_TITLE_OK * nsIPrompt::BUTTON_POS_0,
|
||||
nullptr, nullptr, nullptr,
|
||||
checkboxMessage, &checkboxState, &buttonPressed);
|
||||
|
||||
|
||||
if (NS_SUCCEEDED(rv) && checkboxState)
|
||||
mDontShowBadPluginMessage = true;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsPluginHost::ParsePostBufferToFixHeaders(const char *inPostData, uint32_t inPostDataLen,
|
||||
char **outPostData, uint32_t *outPostDataLen)
|
||||
|
@ -118,7 +118,6 @@ public:
|
||||
|
||||
nsresult GetPluginName(nsNPAPIPluginInstance *aPluginInstance, const char** aPluginName);
|
||||
nsresult StopPluginInstance(nsNPAPIPluginInstance* aInstance);
|
||||
nsresult HandleBadPlugin(PRLibrary* aLibrary, nsNPAPIPluginInstance *aInstance);
|
||||
nsresult GetPluginTagForInstance(nsNPAPIPluginInstance *aPluginInstance, nsIPluginTag **aPluginTag);
|
||||
|
||||
nsresult
|
||||
@ -265,7 +264,6 @@ private:
|
||||
nsRefPtr<nsInvalidPluginTag> mInvalidPlugins;
|
||||
nsTArray<nsCString> mPlayPreviewMimeTypes;
|
||||
bool mPluginsLoaded;
|
||||
bool mDontShowBadPluginMessage;
|
||||
|
||||
// set by pref plugin.override_internal_types
|
||||
bool mOverrideInternalTypes;
|
||||
|
@ -6,8 +6,6 @@
|
||||
#ifndef nsPluginSafety_h_
|
||||
#define nsPluginSafety_h_
|
||||
|
||||
#include "npapi.h"
|
||||
#include "nsPluginHost.h"
|
||||
#include <prinrval.h>
|
||||
|
||||
// On Android, we need to guard against plugin code leaking entries in the local
|
||||
@ -20,75 +18,9 @@
|
||||
#define MAIN_THREAD_JNI_REF_GUARD
|
||||
#endif
|
||||
|
||||
#if defined(XP_WIN)
|
||||
#define CALL_SAFETY_ON
|
||||
#endif
|
||||
|
||||
PRIntervalTime NS_NotifyBeginPluginCall();
|
||||
void NS_NotifyPluginCall(PRIntervalTime);
|
||||
|
||||
#ifdef CALL_SAFETY_ON
|
||||
|
||||
#include "mozilla/Preferences.h"
|
||||
|
||||
extern bool gSkipPluginSafeCalls;
|
||||
|
||||
#define NS_INIT_PLUGIN_SAFE_CALLS \
|
||||
PR_BEGIN_MACRO \
|
||||
gSkipPluginSafeCalls = \
|
||||
::mozilla::Preferences::GetBool("plugin.dont_try_safe_calls", \
|
||||
gSkipPluginSafeCalls); \
|
||||
PR_END_MACRO
|
||||
|
||||
#define NS_TRY_SAFE_CALL_RETURN(ret, fun, pluginInst) \
|
||||
PR_BEGIN_MACRO \
|
||||
MAIN_THREAD_JNI_REF_GUARD; \
|
||||
PRIntervalTime startTime = NS_NotifyBeginPluginCall(); \
|
||||
if(gSkipPluginSafeCalls) \
|
||||
ret = fun; \
|
||||
else \
|
||||
{ \
|
||||
MOZ_SEH_TRY \
|
||||
{ \
|
||||
ret = fun; \
|
||||
} \
|
||||
MOZ_SEH_EXCEPT(true) \
|
||||
{ \
|
||||
nsresult res; \
|
||||
nsCOMPtr<nsIPluginHost> host(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID, &res));\
|
||||
if(NS_SUCCEEDED(res) && (host != nullptr)) \
|
||||
static_cast<nsPluginHost*>(host.get())->HandleBadPlugin(nullptr, pluginInst); \
|
||||
ret = (NPError)NS_ERROR_FAILURE; \
|
||||
} \
|
||||
} \
|
||||
NS_NotifyPluginCall(startTime); \
|
||||
PR_END_MACRO
|
||||
|
||||
#define NS_TRY_SAFE_CALL_VOID(fun, pluginInst) \
|
||||
PR_BEGIN_MACRO \
|
||||
MAIN_THREAD_JNI_REF_GUARD; \
|
||||
PRIntervalTime startTime = NS_NotifyBeginPluginCall(); \
|
||||
if(gSkipPluginSafeCalls) \
|
||||
fun; \
|
||||
else \
|
||||
{ \
|
||||
MOZ_SEH_TRY \
|
||||
{ \
|
||||
fun; \
|
||||
} \
|
||||
MOZ_SEH_EXCEPT(true) \
|
||||
{ \
|
||||
nsresult res; \
|
||||
nsCOMPtr<nsIPluginHost> host(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID, &res));\
|
||||
if(NS_SUCCEEDED(res) && (host != nullptr))\
|
||||
static_cast<nsPluginHost*>(host.get())->HandleBadPlugin(nullptr, pluginInst);\
|
||||
} \
|
||||
} \
|
||||
NS_NotifyPluginCall(startTime); \
|
||||
PR_END_MACRO
|
||||
|
||||
#else // vanilla calls
|
||||
|
||||
#define NS_TRY_SAFE_CALL_RETURN(ret, fun, pluginInst) \
|
||||
PR_BEGIN_MACRO \
|
||||
MAIN_THREAD_JNI_REF_GUARD; \
|
||||
@ -105,6 +37,4 @@ PR_BEGIN_MACRO \
|
||||
NS_NotifyPluginCall(startTime); \
|
||||
PR_END_MACRO
|
||||
|
||||
#endif // CALL_SAFETY_ON
|
||||
|
||||
#endif //nsPluginSafety_h_
|
||||
|
@ -533,7 +533,8 @@ PluginModuleParent::EvaluateHangUIState(const bool aReset)
|
||||
bool
|
||||
PluginModuleParent::GetPluginName(nsAString& aPluginName)
|
||||
{
|
||||
nsPluginHost* host = nsPluginHost::GetInst();
|
||||
nsRefPtr<nsPluginHost> host =
|
||||
dont_AddRef<nsPluginHost>(nsPluginHost::GetInst());
|
||||
if (!host) {
|
||||
return false;
|
||||
}
|
||||
|
25
dom/webidl/HTMLProgressElement.webidl
Normal file
25
dom/webidl/HTMLProgressElement.webidl
Normal file
@ -0,0 +1,25 @@
|
||||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* The origin of this IDL file is
|
||||
* http://www.whatwg.org/specs/web-apps/current-work/#the-progress-element
|
||||
*
|
||||
* © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and
|
||||
* Opera Software ASA. You are granted a license to use, reproduce
|
||||
* and create derivative works of this document.
|
||||
*/
|
||||
|
||||
interface HTMLProgressElement : HTMLElement {
|
||||
[SetterThrows]
|
||||
attribute double value;
|
||||
[SetterThrows]
|
||||
attribute double max;
|
||||
readonly attribute double position;
|
||||
|
||||
/**
|
||||
* The labels attribute will be done with bug 567740.
|
||||
*/
|
||||
//readonly attribute NodeList labels;
|
||||
};
|
@ -90,6 +90,7 @@ webidl_files = \
|
||||
HTMLParagraphElement.webidl \
|
||||
HTMLParamElement.webidl \
|
||||
HTMLPreElement.webidl \
|
||||
HTMLProgressElement.webidl \
|
||||
HTMLPropertiesCollection.webidl \
|
||||
HTMLQuoteElement.webidl \
|
||||
HTMLScriptElement.webidl \
|
||||
|
@ -323,11 +323,16 @@ class DataSourceSurface : public SourceSurface
|
||||
{
|
||||
public:
|
||||
virtual SurfaceType GetType() const { return SURFACE_DATA; }
|
||||
/* Get the raw bitmap data of the surface */
|
||||
/*
|
||||
* Get the raw bitmap data of the surface.
|
||||
* Can return null if there was OOM allocating surface data.
|
||||
*/
|
||||
virtual uint8_t *GetData() = 0;
|
||||
|
||||
/*
|
||||
* Stride of the surface, distance in bytes between the start of the image
|
||||
* data belonging to row y and row y+1. This may be negative.
|
||||
* Can return 0 if there was OOM allocating surface data.
|
||||
*/
|
||||
virtual int32_t Stride() = 0;
|
||||
|
||||
|
@ -521,6 +521,9 @@ LayerManagerOGL::Initialize(nsRefPtr<GLContext> aContext, bool force)
|
||||
|
||||
// initialise a common shader to check that we can actually compile a shader
|
||||
if (!mPrograms[gl::RGBALayerProgramType].mVariations[MaskNone]->Initialize()) {
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
NS_RUNTIMEABORT("Shader initialization failed");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -591,6 +594,9 @@ LayerManagerOGL::Initialize(nsRefPtr<GLContext> aContext, bool force)
|
||||
|
||||
if (mFBOTextureTarget == LOCAL_GL_NONE) {
|
||||
/* Unable to find a texture target that works with FBOs and NPOT textures */
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
NS_RUNTIMEABORT("No texture target");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@ -608,6 +614,9 @@ LayerManagerOGL::Initialize(nsRefPtr<GLContext> aContext, bool force)
|
||||
* texture2DRect).
|
||||
*/
|
||||
if (!mGLContext->IsExtensionSupported(gl::GLContext::ARB_texture_rectangle))
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
NS_RUNTIMEABORT("No texture rectangle");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1209,7 +1209,11 @@ qcms_transform* qcms_transform_create(
|
||||
precache = true;
|
||||
}
|
||||
|
||||
if (qcms_supports_iccv4 && (in->A2B0 || out->B2A0 || in->mAB || out->mAB)) {
|
||||
// This precache assumes RGB_SIGNATURE (fails on GRAY_SIGNATURE, for instance)
|
||||
if (qcms_supports_iccv4 &&
|
||||
(in_type == QCMS_DATA_RGB_8 || in_type == QCMS_DATA_RGBA_8) &&
|
||||
(in->A2B0 || out->B2A0 || in->mAB || out->mAB))
|
||||
{
|
||||
// Precache the transformation to a CLUT 33x33x33 in size.
|
||||
// 33 is used by many profiles and works well in pratice.
|
||||
// This evenly divides 256 into blocks of 8x8x8.
|
||||
|
@ -33,7 +33,7 @@ using namespace mozilla::services;
|
||||
#define PROXY_IF_SANDBOXED(_call) \
|
||||
do { \
|
||||
if (InSandbox()) { \
|
||||
if (!hal_sandbox::IsHalChildLive()) { \
|
||||
if (!hal_sandbox::HalChildDestroyed()) { \
|
||||
hal_sandbox::_call; \
|
||||
} \
|
||||
} else { \
|
||||
@ -44,7 +44,7 @@ using namespace mozilla::services;
|
||||
#define RETURN_PROXY_IF_SANDBOXED(_call, defValue)\
|
||||
do { \
|
||||
if (InSandbox()) { \
|
||||
if (hal_sandbox::IsHalChildLive()) { \
|
||||
if (hal_sandbox::HalChildDestroyed()) { \
|
||||
return defValue; \
|
||||
} \
|
||||
return hal_sandbox::_call; \
|
||||
|
@ -94,7 +94,11 @@ void EnableSystemTimezoneChangeNotifications();
|
||||
*/
|
||||
void DisableSystemTimezoneChangeNotifications();
|
||||
|
||||
bool IsHalChildLive();
|
||||
/**
|
||||
* Has the child-side HAL IPC object been destroyed? If so, you shouldn't send
|
||||
* messages to hal_sandbox.
|
||||
*/
|
||||
bool HalChildDestroyed();
|
||||
} // namespace MOZ_HAL_NAMESPACE
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -25,12 +25,12 @@ using namespace mozilla::hal;
|
||||
namespace mozilla {
|
||||
namespace hal_sandbox {
|
||||
|
||||
static bool sHalChildIsLive = false;
|
||||
static bool sHalChildDestroyed = false;
|
||||
|
||||
bool
|
||||
IsHalChildLive()
|
||||
HalChildDestroyed()
|
||||
{
|
||||
return sHalChildIsLive;
|
||||
return sHalChildDestroyed;
|
||||
}
|
||||
|
||||
static PHalChild* sHal;
|
||||
@ -810,7 +810,7 @@ public:
|
||||
virtual void
|
||||
ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE
|
||||
{
|
||||
sHalChildIsLive = true;
|
||||
sHalChildDestroyed = true;
|
||||
}
|
||||
|
||||
virtual bool
|
||||
|
@ -1,64 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/clipboard.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// A compromised renderer could send us bad data, so validate it.
|
||||
bool IsBitmapSafe(const Clipboard::ObjectMapParams& params) {
|
||||
const gfx::Size* size =
|
||||
reinterpret_cast<const gfx::Size*>(&(params[1].front()));
|
||||
return params[0].size() ==
|
||||
static_cast<size_t>(size->width() * size->height() * 4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Clipboard::DispatchObject(ObjectType type, const ObjectMapParams& params) {
|
||||
switch (type) {
|
||||
case CBF_TEXT:
|
||||
WriteText(&(params[0].front()), params[0].size());
|
||||
break;
|
||||
|
||||
case CBF_HTML:
|
||||
if (params.size() == 2)
|
||||
WriteHTML(&(params[0].front()), params[0].size(),
|
||||
&(params[1].front()), params[1].size());
|
||||
else
|
||||
WriteHTML(&(params[0].front()), params[0].size(), NULL, 0);
|
||||
break;
|
||||
|
||||
case CBF_BOOKMARK:
|
||||
WriteBookmark(&(params[0].front()), params[0].size(),
|
||||
&(params[1].front()), params[1].size());
|
||||
break;
|
||||
|
||||
case CBF_LINK:
|
||||
WriteHyperlink(&(params[0].front()), params[0].size(),
|
||||
&(params[1].front()), params[1].size());
|
||||
break;
|
||||
|
||||
case CBF_FILES:
|
||||
WriteFiles(&(params[0].front()), params[0].size());
|
||||
break;
|
||||
|
||||
case CBF_WEBKIT:
|
||||
WriteWebSmartPaste();
|
||||
break;
|
||||
|
||||
#if defined(OS_WIN) || defined(OS_LINUX)
|
||||
case CBF_BITMAP:
|
||||
if (!IsBitmapSafe(params))
|
||||
return;
|
||||
WriteBitmap(&(params[0].front()), &(params[1].front()));
|
||||
break;
|
||||
#endif // defined(OS_WIN) || defined(OS_LINUX)
|
||||
|
||||
default:
|
||||
NOTREACHED();
|
||||
}
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_CLIPBOARD_H_
|
||||
#define BASE_CLIPBOARD_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/file_path.h"
|
||||
#include "base/process.h"
|
||||
#include "base/string16.h"
|
||||
#include "base/gfx/size.h"
|
||||
|
||||
class Clipboard {
|
||||
public:
|
||||
typedef std::string FormatType;
|
||||
#if defined(OS_LINUX)
|
||||
typedef struct _GtkClipboard GtkClipboard;
|
||||
typedef std::map<FormatType, std::pair<char*, size_t> > TargetMap;
|
||||
#endif
|
||||
|
||||
// ObjectType designates the type of data to be stored in the clipboard. This
|
||||
// designation is shared across all OSes. The system-specific designation
|
||||
// is defined by FormatType. A single ObjectType might be represented by
|
||||
// several system-specific FormatTypes. For example, on Linux the CBF_TEXT
|
||||
// ObjectType maps to "text/plain", "STRING", and several other formats. On
|
||||
// windows it maps to CF_UNICODETEXT.
|
||||
enum ObjectType {
|
||||
CBF_TEXT,
|
||||
CBF_HTML,
|
||||
CBF_BOOKMARK,
|
||||
CBF_LINK,
|
||||
CBF_FILES,
|
||||
CBF_WEBKIT,
|
||||
CBF_BITMAP,
|
||||
CBF_SMBITMAP // bitmap from shared memory
|
||||
};
|
||||
|
||||
// ObjectMap is a map from ObjectType to associated data.
|
||||
// The data is organized differently for each ObjectType. The following
|
||||
// table summarizes what kind of data is stored for each key.
|
||||
// * indicates an optional argument.
|
||||
//
|
||||
// Key Arguments Type
|
||||
// -------------------------------------
|
||||
// CBF_TEXT text char array
|
||||
// CBF_HTML html char array
|
||||
// url* char array
|
||||
// CBF_BOOKMARK html char array
|
||||
// url char array
|
||||
// CBF_LINK html char array
|
||||
// url char array
|
||||
// CBF_FILES files char array representing multiple files.
|
||||
// Filenames are separated by null characters and
|
||||
// the final filename is double null terminated.
|
||||
// The filenames are encoded in platform-specific
|
||||
// encoding.
|
||||
// CBF_WEBKIT none empty vector
|
||||
// CBF_BITMAP pixels byte array
|
||||
// size gfx::Size struct
|
||||
// CBF_SMBITMAP shared_mem shared memory handle
|
||||
// size gfx::Size struct
|
||||
typedef std::vector<char> ObjectMapParam;
|
||||
typedef std::vector<ObjectMapParam> ObjectMapParams;
|
||||
typedef std::map<int /* ObjectType */, ObjectMapParams> ObjectMap;
|
||||
|
||||
Clipboard();
|
||||
~Clipboard();
|
||||
|
||||
// Write a bunch of objects to the system clipboard. Copies are made of the
|
||||
// contents of |objects|. On Windows they are copied to the system clipboard.
|
||||
// On linux they are copied into a structure owned by the Clipboard object and
|
||||
// kept until the system clipboard is set again.
|
||||
void WriteObjects(const ObjectMap& objects);
|
||||
|
||||
// Behaves as above. If there is some shared memory handle passed as one of
|
||||
// the objects, it came from the process designated by |process|. This will
|
||||
// assist in turning it into a shared memory region that the current process
|
||||
// can use.
|
||||
void WriteObjects(const ObjectMap& objects, base::ProcessHandle process);
|
||||
|
||||
// Tests whether the clipboard contains a certain format
|
||||
bool IsFormatAvailable(const FormatType& format) const;
|
||||
|
||||
// Reads UNICODE text from the clipboard, if available.
|
||||
void ReadText(string16* result) const;
|
||||
|
||||
// Reads ASCII text from the clipboard, if available.
|
||||
void ReadAsciiText(std::string* result) const;
|
||||
|
||||
// Reads HTML from the clipboard, if available.
|
||||
void ReadHTML(string16* markup, std::string* src_url) const;
|
||||
|
||||
// Reads a bookmark from the clipboard, if available.
|
||||
void ReadBookmark(string16* title, std::string* url) const;
|
||||
|
||||
// Reads a file or group of files from the clipboard, if available, into the
|
||||
// out parameter.
|
||||
void ReadFile(FilePath* file) const;
|
||||
void ReadFiles(std::vector<FilePath>* files) const;
|
||||
|
||||
// Get format Identifiers for various types.
|
||||
static FormatType GetUrlFormatType();
|
||||
static FormatType GetUrlWFormatType();
|
||||
static FormatType GetMozUrlFormatType();
|
||||
static FormatType GetPlainTextFormatType();
|
||||
static FormatType GetPlainTextWFormatType();
|
||||
static FormatType GetFilenameFormatType();
|
||||
static FormatType GetFilenameWFormatType();
|
||||
static FormatType GetWebKitSmartPasteFormatType();
|
||||
// Win: MS HTML Format, Other: Generic HTML format
|
||||
static FormatType GetHtmlFormatType();
|
||||
#if defined(OS_WIN)
|
||||
static FormatType GetBitmapFormatType();
|
||||
// Firefox text/html
|
||||
static FormatType GetTextHtmlFormatType();
|
||||
static FormatType GetCFHDropFormatType();
|
||||
static FormatType GetFileDescriptorFormatType();
|
||||
static FormatType GetFileContentFormatZeroType();
|
||||
|
||||
// Duplicates any remote shared memory handle embedded inside |objects| that
|
||||
// was created by |process| so that it can be used by this process.
|
||||
static void DuplicateRemoteHandles(base::ProcessHandle process,
|
||||
ObjectMap* objects);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void WriteText(const char* text_data, size_t text_len);
|
||||
|
||||
void WriteHTML(const char* markup_data,
|
||||
size_t markup_len,
|
||||
const char* url_data,
|
||||
size_t url_len);
|
||||
|
||||
void WriteBookmark(const char* title_data,
|
||||
size_t title_len,
|
||||
const char* url_data,
|
||||
size_t url_len);
|
||||
|
||||
void WriteHyperlink(const char* title_data,
|
||||
size_t title_len,
|
||||
const char* url_data,
|
||||
size_t url_len);
|
||||
|
||||
void WriteWebSmartPaste();
|
||||
|
||||
void WriteFiles(const char* file_data, size_t file_len);
|
||||
|
||||
void DispatchObject(ObjectType type, const ObjectMapParams& params);
|
||||
|
||||
void WriteBitmap(const char* pixel_data, const char* size_data);
|
||||
#if defined(OS_WIN)
|
||||
void WriteBitmapFromSharedMemory(const char* bitmap_data,
|
||||
const char* size_data,
|
||||
base::ProcessHandle handle);
|
||||
|
||||
void WriteBitmapFromHandle(HBITMAP source_hbitmap,
|
||||
const gfx::Size& size);
|
||||
|
||||
// Safely write to system clipboard. Free |handle| on failure.
|
||||
void WriteToClipboard(unsigned int format, HANDLE handle);
|
||||
|
||||
static void ParseBookmarkClipboardFormat(const string16& bookmark,
|
||||
string16* title,
|
||||
std::string* url);
|
||||
|
||||
// Free a handle depending on its type (as intuited from format)
|
||||
static void FreeData(unsigned int format, HANDLE data);
|
||||
|
||||
// Return the window that should be the clipboard owner, creating it
|
||||
// if neccessary. Marked const for lazily initialization by const methods.
|
||||
HWND GetClipboardWindow() const;
|
||||
|
||||
// Mark this as mutable so const methods can still do lazy initialization.
|
||||
mutable HWND clipboard_owner_;
|
||||
|
||||
// True if we can create a window.
|
||||
bool create_window_;
|
||||
#elif defined(OS_LINUX)
|
||||
// Data is stored in the |clipboard_data_| map until it is saved to the system
|
||||
// clipboard. The Store* functions save data to the |clipboard_data_| map. The
|
||||
// SetGtkClipboard function replaces whatever is on the system clipboard with
|
||||
// the contents of |clipboard_data_|.
|
||||
// The Write* functions make a deep copy of the data passed to them an store
|
||||
// it in |clipboard_data_|.
|
||||
|
||||
// Write changes to gtk clipboard.
|
||||
void SetGtkClipboard();
|
||||
// Free pointers in clipboard_data_ and clear() the map.
|
||||
void FreeTargetMap();
|
||||
// Insert a mapping into clipboard_data_.
|
||||
void InsertMapping(const char* key, char* data, size_t data_len);
|
||||
|
||||
TargetMap* clipboard_data_;
|
||||
GtkClipboard* clipboard_;
|
||||
#endif
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(Clipboard);
|
||||
};
|
||||
|
||||
#endif // BASE_CLIPBOARD_H_
|
@ -1,328 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/clipboard.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "base/linux_util.h"
|
||||
#include "base/string_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const char kMimeBmp[] = "image/bmp";
|
||||
const char kMimeHtml[] = "text/html";
|
||||
const char kMimeText[] = "text/plain";
|
||||
const char kMimeWebkitSmartPaste[] = "chromium-internal/webkit-paste";
|
||||
|
||||
std::string GdkAtomToString(const GdkAtom& atom) {
|
||||
gchar* name = gdk_atom_name(atom);
|
||||
std::string rv(name);
|
||||
g_free(name);
|
||||
return rv;
|
||||
}
|
||||
|
||||
GdkAtom StringToGdkAtom(const std::string& str) {
|
||||
return gdk_atom_intern(str.c_str(), false);
|
||||
}
|
||||
|
||||
// GtkClipboardGetFunc callback.
|
||||
// GTK will call this when an application wants data we copied to the clipboard.
|
||||
void GetData(GtkClipboard* clipboard,
|
||||
GtkSelectionData* selection_data,
|
||||
guint info,
|
||||
gpointer user_data) {
|
||||
Clipboard::TargetMap* data_map =
|
||||
reinterpret_cast<Clipboard::TargetMap*>(user_data);
|
||||
|
||||
std::string target_string = GdkAtomToString(selection_data->target);
|
||||
Clipboard::TargetMap::iterator iter = data_map->find(target_string);
|
||||
|
||||
if (iter == data_map->end())
|
||||
return;
|
||||
|
||||
if (target_string == kMimeBmp) {
|
||||
gtk_selection_data_set_pixbuf(selection_data,
|
||||
reinterpret_cast<GdkPixbuf*>(iter->second.first));
|
||||
} else {
|
||||
gtk_selection_data_set(selection_data, selection_data->target, 8,
|
||||
reinterpret_cast<guchar*>(iter->second.first),
|
||||
iter->second.second);
|
||||
}
|
||||
}
|
||||
|
||||
// GtkClipboardClearFunc callback.
|
||||
// We are guaranteed this will be called exactly once for each call to
|
||||
// gtk_clipboard_set_with_data
|
||||
void ClearData(GtkClipboard* clipboard,
|
||||
gpointer user_data) {
|
||||
Clipboard::TargetMap* map =
|
||||
reinterpret_cast<Clipboard::TargetMap*>(user_data);
|
||||
std::set<char*> ptrs;
|
||||
|
||||
for (Clipboard::TargetMap::iterator iter = map->begin();
|
||||
iter != map->end(); ++iter) {
|
||||
if (iter->first == kMimeBmp)
|
||||
g_object_unref(reinterpret_cast<GdkPixbuf*>(iter->second.first));
|
||||
else
|
||||
ptrs.insert(iter->second.first);
|
||||
}
|
||||
|
||||
for (std::set<char*>::iterator iter = ptrs.begin();
|
||||
iter != ptrs.end(); ++iter)
|
||||
delete[] *iter;
|
||||
|
||||
delete map;
|
||||
}
|
||||
|
||||
// Frees the pointers in the given map and clears the map.
|
||||
// Does not double-free any pointers.
|
||||
void FreeTargetMap(Clipboard::TargetMap map) {
|
||||
std::set<char*> ptrs;
|
||||
|
||||
for (Clipboard::TargetMap::iterator iter = map.begin();
|
||||
iter != map.end(); ++iter)
|
||||
ptrs.insert(iter->second.first);
|
||||
|
||||
for (std::set<char*>::iterator iter = ptrs.begin();
|
||||
iter != ptrs.end(); ++iter)
|
||||
delete[] *iter;
|
||||
|
||||
map.clear();
|
||||
}
|
||||
|
||||
// Called on GdkPixbuf destruction; see WriteBitmap().
|
||||
void GdkPixbufFree(guchar* pixels, gpointer data) {
|
||||
free(pixels);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Clipboard::Clipboard() {
|
||||
clipboard_ = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
|
||||
}
|
||||
|
||||
Clipboard::~Clipboard() {
|
||||
// TODO(estade): do we want to save clipboard data after we exit?
|
||||
// gtk_clipboard_set_can_store and gtk_clipboard_store work
|
||||
// but have strangely awful performance.
|
||||
}
|
||||
|
||||
void Clipboard::WriteObjects(const ObjectMap& objects) {
|
||||
clipboard_data_ = new TargetMap();
|
||||
|
||||
for (ObjectMap::const_iterator iter = objects.begin();
|
||||
iter != objects.end(); ++iter) {
|
||||
DispatchObject(static_cast<ObjectType>(iter->first), iter->second);
|
||||
}
|
||||
|
||||
SetGtkClipboard();
|
||||
}
|
||||
|
||||
// Take ownership of the GTK clipboard and inform it of the targets we support.
|
||||
void Clipboard::SetGtkClipboard() {
|
||||
scoped_array<GtkTargetEntry> targets(
|
||||
new GtkTargetEntry[clipboard_data_->size()]);
|
||||
|
||||
int i = 0;
|
||||
for (Clipboard::TargetMap::iterator iter = clipboard_data_->begin();
|
||||
iter != clipboard_data_->end(); ++iter, ++i) {
|
||||
char* target_string = new char[iter->first.size() + 1];
|
||||
strcpy(target_string, iter->first.c_str());
|
||||
targets[i].target = target_string;
|
||||
targets[i].flags = 0;
|
||||
targets[i].info = i;
|
||||
}
|
||||
|
||||
gtk_clipboard_set_with_data(clipboard_, targets.get(),
|
||||
clipboard_data_->size(),
|
||||
GetData, ClearData,
|
||||
clipboard_data_);
|
||||
|
||||
for (size_t i = 0; i < clipboard_data_->size(); i++)
|
||||
delete[] targets[i].target;
|
||||
}
|
||||
|
||||
void Clipboard::WriteText(const char* text_data, size_t text_len) {
|
||||
char* data = new char[text_len];
|
||||
memcpy(data, text_data, text_len);
|
||||
|
||||
InsertMapping(kMimeText, data, text_len);
|
||||
InsertMapping("TEXT", data, text_len);
|
||||
InsertMapping("STRING", data, text_len);
|
||||
InsertMapping("UTF8_STRING", data, text_len);
|
||||
InsertMapping("COMPOUND_TEXT", data, text_len);
|
||||
}
|
||||
|
||||
void Clipboard::WriteHTML(const char* markup_data,
|
||||
size_t markup_len,
|
||||
const char* url_data,
|
||||
size_t url_len) {
|
||||
// TODO(estade): might not want to ignore |url_data|
|
||||
char* data = new char[markup_len];
|
||||
memcpy(data, markup_data, markup_len);
|
||||
|
||||
InsertMapping(kMimeHtml, data, markup_len);
|
||||
}
|
||||
|
||||
// Write an extra flavor that signifies WebKit was the last to modify the
|
||||
// pasteboard. This flavor has no data.
|
||||
void Clipboard::WriteWebSmartPaste() {
|
||||
InsertMapping(kMimeWebkitSmartPaste, NULL, 0);
|
||||
}
|
||||
|
||||
void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) {
|
||||
const gfx::Size* size = reinterpret_cast<const gfx::Size*>(size_data);
|
||||
|
||||
guchar* data = base::BGRAToRGBA(reinterpret_cast<const uint8_t*>(pixel_data),
|
||||
size->width(), size->height(), 0);
|
||||
|
||||
GdkPixbuf* pixbuf =
|
||||
gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE,
|
||||
8, size->width(), size->height(),
|
||||
size->width() * 4, GdkPixbufFree, NULL);
|
||||
// We store the GdkPixbuf*, and the size_t half of the pair is meaningless.
|
||||
// Note that this contrasts with the vast majority of entries in our target
|
||||
// map, which directly store the data and its length.
|
||||
InsertMapping(kMimeBmp, reinterpret_cast<char*>(pixbuf), 0);
|
||||
}
|
||||
|
||||
void Clipboard::WriteBookmark(const char* title_data, size_t title_len,
|
||||
const char* url_data, size_t url_len) {
|
||||
// TODO(estade): implement this, but for now fail silently so we do not
|
||||
// write error output during layout tests.
|
||||
// NOTIMPLEMENTED();
|
||||
}
|
||||
|
||||
void Clipboard::WriteHyperlink(const char* title_data, size_t title_len,
|
||||
const char* url_data, size_t url_len) {
|
||||
NOTIMPLEMENTED();
|
||||
}
|
||||
|
||||
void Clipboard::WriteFiles(const char* file_data, size_t file_len) {
|
||||
NOTIMPLEMENTED();
|
||||
}
|
||||
|
||||
// We do not use gtk_clipboard_wait_is_target_available because of
|
||||
// a bug with the gtk clipboard. It caches the available targets
|
||||
// and does not always refresh the cache when it is appropriate.
|
||||
// TODO(estade): When gnome bug 557315 is resolved, change this function
|
||||
// to use gtk_clipboard_wait_is_target_available. Also, catch requests
|
||||
// for plain text and change them to gtk_clipboard_wait_is_text_available
|
||||
// (which checks for several standard text targets).
|
||||
bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format) const {
|
||||
bool retval = false;
|
||||
GdkAtom* targets = NULL;
|
||||
GtkSelectionData* data =
|
||||
gtk_clipboard_wait_for_contents(clipboard_,
|
||||
gdk_atom_intern("TARGETS", false));
|
||||
|
||||
if (!data)
|
||||
return false;
|
||||
|
||||
int num = 0;
|
||||
gtk_selection_data_get_targets(data, &targets, &num);
|
||||
|
||||
GdkAtom format_atom = StringToGdkAtom(format);
|
||||
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (targets[i] == format_atom) {
|
||||
retval = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gtk_selection_data_free(data);
|
||||
g_free(targets);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void Clipboard::ReadText(string16* result) const {
|
||||
result->clear();
|
||||
gchar* text = gtk_clipboard_wait_for_text(clipboard_);
|
||||
|
||||
if (text == NULL)
|
||||
return;
|
||||
|
||||
// TODO(estade): do we want to handle the possible error here?
|
||||
UTF8ToUTF16(text, strlen(text), result);
|
||||
g_free(text);
|
||||
}
|
||||
|
||||
void Clipboard::ReadAsciiText(std::string* result) const {
|
||||
result->clear();
|
||||
gchar* text = gtk_clipboard_wait_for_text(clipboard_);
|
||||
|
||||
if (text == NULL)
|
||||
return;
|
||||
|
||||
result->assign(text);
|
||||
g_free(text);
|
||||
}
|
||||
|
||||
void Clipboard::ReadFile(FilePath* file) const {
|
||||
*file = FilePath();
|
||||
}
|
||||
|
||||
// TODO(estade): handle different charsets.
|
||||
void Clipboard::ReadHTML(string16* markup, std::string* src_url) const {
|
||||
markup->clear();
|
||||
|
||||
GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard_,
|
||||
StringToGdkAtom(GetHtmlFormatType()));
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
UTF8ToUTF16(reinterpret_cast<char*>(data->data),
|
||||
strlen(reinterpret_cast<char*>(data->data)),
|
||||
markup);
|
||||
gtk_selection_data_free(data);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetPlainTextFormatType() {
|
||||
return GdkAtomToString(GDK_TARGET_STRING);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetPlainTextWFormatType() {
|
||||
return GetPlainTextFormatType();
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetHtmlFormatType() {
|
||||
return std::string(kMimeHtml);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetWebKitSmartPasteFormatType() {
|
||||
return std::string(kMimeWebkitSmartPaste);
|
||||
}
|
||||
|
||||
// Insert the key/value pair in the clipboard_data structure. If
|
||||
// the mapping already exists, it frees the associated data. Don't worry
|
||||
// about double freeing because if the same key is inserted into the
|
||||
// map twice, it must have come from different Write* functions and the
|
||||
// data pointer cannot be the same.
|
||||
void Clipboard::InsertMapping(const char* key,
|
||||
char* data,
|
||||
size_t data_len) {
|
||||
TargetMap::iterator iter = clipboard_data_->find(key);
|
||||
|
||||
if (iter != clipboard_data_->end()) {
|
||||
if (strcmp(kMimeBmp, key) == 0)
|
||||
g_object_unref(reinterpret_cast<GdkPixbuf*>(iter->second.first));
|
||||
else
|
||||
delete[] iter->second.first;
|
||||
}
|
||||
|
||||
(*clipboard_data_)[key] = std::make_pair(data, data_len);
|
||||
}
|
@ -1,283 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/clipboard.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/string_util.h"
|
||||
#include "base/sys_string_conversions.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Would be nice if this were in UTCoreTypes.h, but it isn't
|
||||
const NSString* kUTTypeURLName = @"public.url-name";
|
||||
|
||||
// Tells us if WebKit was the last to write to the pasteboard. There's no
|
||||
// actual data associated with this type.
|
||||
const NSString *kWebSmartPastePboardType = @"NeXT smart paste pasteboard type";
|
||||
|
||||
NSPasteboard* GetPasteboard() {
|
||||
// The pasteboard should not be nil in a UI session, but this handy DCHECK
|
||||
// can help track down problems if someone tries using clipboard code outside
|
||||
// of a UI session.
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
DCHECK(pasteboard);
|
||||
return pasteboard;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Clipboard::Clipboard() {
|
||||
}
|
||||
|
||||
Clipboard::~Clipboard() {
|
||||
}
|
||||
|
||||
void Clipboard::WriteObjects(const ObjectMap& objects) {
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
[pb declareTypes:[NSArray array] owner:nil];
|
||||
|
||||
for (ObjectMap::const_iterator iter = objects.begin();
|
||||
iter != objects.end(); ++iter) {
|
||||
DispatchObject(static_cast<ObjectType>(iter->first), iter->second);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Clipboard::WriteText(const char* text_data, size_t text_len) {
|
||||
std::string text_str(text_data, text_len);
|
||||
NSString *text = base::SysUTF8ToNSString(text_str);
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
[pb addTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
|
||||
[pb setString:text forType:NSStringPboardType];
|
||||
}
|
||||
|
||||
void Clipboard::WriteHTML(const char* markup_data,
|
||||
size_t markup_len,
|
||||
const char* url_data,
|
||||
size_t url_len) {
|
||||
std::string html_fragment_str(markup_data, markup_len);
|
||||
NSString *html_fragment = base::SysUTF8ToNSString(html_fragment_str);
|
||||
|
||||
// TODO(avi): url_data?
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
[pb addTypes:[NSArray arrayWithObject:NSHTMLPboardType] owner:nil];
|
||||
[pb setString:html_fragment forType:NSHTMLPboardType];
|
||||
}
|
||||
|
||||
void Clipboard::WriteBookmark(const char* title_data,
|
||||
size_t title_len,
|
||||
const char* url_data,
|
||||
size_t url_len) {
|
||||
WriteHyperlink(title_data, title_len, url_data, url_len);
|
||||
}
|
||||
|
||||
void Clipboard::WriteHyperlink(const char* title_data,
|
||||
size_t title_len,
|
||||
const char* url_data,
|
||||
size_t url_len) {
|
||||
std::string title_str(title_data, title_len);
|
||||
NSString *title = base::SysUTF8ToNSString(title_str);
|
||||
std::string url_str(url_data, url_len);
|
||||
NSString *url = base::SysUTF8ToNSString(url_str);
|
||||
|
||||
// TODO(playmobil): In the Windows version of this function, an HTML
|
||||
// representation of the bookmark is also added to the clipboard, to support
|
||||
// drag and drop of web shortcuts. I don't think we need to do this on the
|
||||
// Mac, but we should double check later on.
|
||||
NSURL* nsurl = [NSURL URLWithString:url];
|
||||
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
// passing UTIs into the pasteboard methods is valid >= 10.5
|
||||
[pb addTypes:[NSArray arrayWithObjects:NSURLPboardType,
|
||||
kUTTypeURLName,
|
||||
nil]
|
||||
owner:nil];
|
||||
[nsurl writeToPasteboard:pb];
|
||||
[pb setString:title forType:kUTTypeURLName];
|
||||
}
|
||||
|
||||
void Clipboard::WriteFiles(const char* file_data, size_t file_len) {
|
||||
NSMutableArray* fileList = [NSMutableArray arrayWithCapacity:1];
|
||||
|
||||
// Offset of current filename from start of file_data array.
|
||||
size_t current_filename_offset = 0;
|
||||
|
||||
// file_data is double null terminated (see table at top of clipboard.h).
|
||||
// So this loop can ignore the second null terminator, thus file_len - 1.
|
||||
// TODO(playmobil): If we need a loop like this on other platforms then split
|
||||
// this out into a common function that outputs a std::vector<const char*>.
|
||||
for (size_t i = 0; i < file_len - 1; ++i) {
|
||||
if (file_data[i] == '\0') {
|
||||
const char* filename = &file_data[current_filename_offset];
|
||||
[fileList addObject:[NSString stringWithUTF8String:filename]];
|
||||
|
||||
current_filename_offset = i + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
[pb addTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil];
|
||||
[pb setPropertyList:fileList forType:NSFilenamesPboardType];
|
||||
}
|
||||
|
||||
// Write an extra flavor that signifies WebKit was the last to modify the
|
||||
// pasteboard. This flavor has no data.
|
||||
void Clipboard::WriteWebSmartPaste() {
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
NSString* format = base::SysUTF8ToNSString(GetWebKitSmartPasteFormatType());
|
||||
[pb addTypes:[NSArray arrayWithObject:format] owner:nil];
|
||||
[pb setData:nil forType:format];
|
||||
}
|
||||
|
||||
bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format) const {
|
||||
NSString* format_ns = base::SysUTF8ToNSString(format);
|
||||
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
NSArray* types = [pb types];
|
||||
|
||||
return [types containsObject:format_ns];
|
||||
}
|
||||
|
||||
void Clipboard::ReadText(string16* result) const {
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
NSString* contents = [pb stringForType:NSStringPboardType];
|
||||
|
||||
UTF8ToUTF16([contents UTF8String],
|
||||
[contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding],
|
||||
result);
|
||||
}
|
||||
|
||||
void Clipboard::ReadAsciiText(std::string* result) const {
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
NSString* contents = [pb stringForType:NSStringPboardType];
|
||||
|
||||
if (!contents)
|
||||
result->clear();
|
||||
else
|
||||
result->assign([contents UTF8String]);
|
||||
}
|
||||
|
||||
void Clipboard::ReadHTML(string16* markup, std::string* src_url) const {
|
||||
if (markup) {
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
NSArray *supportedTypes = [NSArray arrayWithObjects:NSHTMLPboardType,
|
||||
NSStringPboardType,
|
||||
nil];
|
||||
NSString *bestType = [pb availableTypeFromArray:supportedTypes];
|
||||
NSString* contents = [pb stringForType:bestType];
|
||||
UTF8ToUTF16([contents UTF8String],
|
||||
[contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding],
|
||||
markup);
|
||||
}
|
||||
|
||||
// TODO(avi): src_url?
|
||||
if (src_url)
|
||||
src_url->clear();
|
||||
}
|
||||
|
||||
void Clipboard::ReadBookmark(string16* title, std::string* url) const {
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
|
||||
if (title) {
|
||||
NSString* contents = [pb stringForType:kUTTypeURLName];
|
||||
UTF8ToUTF16([contents UTF8String],
|
||||
[contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding],
|
||||
title);
|
||||
}
|
||||
|
||||
if (url) {
|
||||
NSString* url_string = [[NSURL URLFromPasteboard:pb] absoluteString];
|
||||
if (!url_string)
|
||||
url->clear();
|
||||
else
|
||||
url->assign([url_string UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
void Clipboard::ReadFile(FilePath* file) const {
|
||||
if (!file) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
|
||||
*file = FilePath();
|
||||
std::vector<FilePath> files;
|
||||
ReadFiles(&files);
|
||||
|
||||
// Take the first file, if available.
|
||||
if (!files.empty())
|
||||
*file = files[0];
|
||||
}
|
||||
|
||||
void Clipboard::ReadFiles(std::vector<FilePath>* files) const {
|
||||
if (!files) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
|
||||
files->clear();
|
||||
|
||||
NSPasteboard* pb = GetPasteboard();
|
||||
NSArray* fileList = [pb propertyListForType:NSFilenamesPboardType];
|
||||
|
||||
for (unsigned int i = 0; i < [fileList count]; ++i) {
|
||||
std::string file = [[fileList objectAtIndex:i] UTF8String];
|
||||
files->push_back(FilePath(file));
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetUrlFormatType() {
|
||||
static const std::string type = base::SysNSStringToUTF8(NSURLPboardType);
|
||||
return type;
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetUrlWFormatType() {
|
||||
static const std::string type = base::SysNSStringToUTF8(NSURLPboardType);
|
||||
return type;
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetPlainTextFormatType() {
|
||||
static const std::string type = base::SysNSStringToUTF8(NSStringPboardType);
|
||||
return type;
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetPlainTextWFormatType() {
|
||||
static const std::string type = base::SysNSStringToUTF8(NSStringPboardType);
|
||||
return type;
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetFilenameFormatType() {
|
||||
static const std::string type =
|
||||
base::SysNSStringToUTF8(NSFilenamesPboardType);
|
||||
return type;
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetFilenameWFormatType() {
|
||||
static const std::string type =
|
||||
base::SysNSStringToUTF8(NSFilenamesPboardType);
|
||||
return type;
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetHtmlFormatType() {
|
||||
static const std::string type = base::SysNSStringToUTF8(NSHTMLPboardType);
|
||||
return type;
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetWebKitSmartPasteFormatType() {
|
||||
static const std::string type =
|
||||
base::SysNSStringToUTF8(kWebSmartPastePboardType);
|
||||
return type;
|
||||
}
|
@ -1,282 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/clipboard.h"
|
||||
#include "base/message_loop.h"
|
||||
#include "base/scoped_clipboard_writer.h"
|
||||
#include "base/string_util.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "testing/platform_test.h"
|
||||
|
||||
#if defined(OS_WIN)
|
||||
class ClipboardTest : public PlatformTest {
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
message_loop_.reset(new MessageLoopForUI());
|
||||
}
|
||||
virtual void TearDown() {
|
||||
}
|
||||
|
||||
private:
|
||||
scoped_ptr<MessageLoop> message_loop_;
|
||||
};
|
||||
#elif defined(OS_POSIX)
|
||||
typedef PlatformTest ClipboardTest;
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
TEST_F(ClipboardTest, ClearTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteText(ASCIIToUTF16("clear me"));
|
||||
}
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteHTML(ASCIIToUTF16("<b>broom</b>"), "");
|
||||
}
|
||||
|
||||
EXPECT_FALSE(clipboard.IsFormatAvailable(
|
||||
Clipboard::GetPlainTextWFormatType()));
|
||||
EXPECT_FALSE(clipboard.IsFormatAvailable(
|
||||
Clipboard::GetPlainTextFormatType()));
|
||||
}
|
||||
|
||||
TEST_F(ClipboardTest, TextTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
string16 text(ASCIIToUTF16("This is a string16!#$")), text_result;
|
||||
std::string ascii_text;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteText(text);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(clipboard.IsFormatAvailable(
|
||||
Clipboard::GetPlainTextWFormatType()));
|
||||
EXPECT_TRUE(clipboard.IsFormatAvailable(
|
||||
Clipboard::GetPlainTextFormatType()));
|
||||
clipboard.ReadText(&text_result);
|
||||
|
||||
EXPECT_EQ(text, text_result);
|
||||
clipboard.ReadAsciiText(&ascii_text);
|
||||
EXPECT_EQ(UTF16ToUTF8(text), ascii_text);
|
||||
}
|
||||
|
||||
TEST_F(ClipboardTest, HTMLTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
string16 markup(ASCIIToUTF16("<string>Hi!</string>")), markup_result;
|
||||
std::string url("http://www.example.com/"), url_result;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteHTML(markup, url);
|
||||
}
|
||||
|
||||
EXPECT_EQ(true, clipboard.IsFormatAvailable(
|
||||
Clipboard::GetHtmlFormatType()));
|
||||
clipboard.ReadHTML(&markup_result, &url_result);
|
||||
EXPECT_EQ(markup, markup_result);
|
||||
#if defined(OS_WIN)
|
||||
// TODO(playmobil): It's not clear that non windows clipboards need to support
|
||||
// this.
|
||||
EXPECT_EQ(url, url_result);
|
||||
#endif // defined(OS_WIN)
|
||||
}
|
||||
|
||||
TEST_F(ClipboardTest, TrickyHTMLTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
string16 markup(ASCIIToUTF16("<em>Bye!<!--EndFragment --></em>")),
|
||||
markup_result;
|
||||
std::string url, url_result;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteHTML(markup, url);
|
||||
}
|
||||
|
||||
EXPECT_EQ(true, clipboard.IsFormatAvailable(
|
||||
Clipboard::GetHtmlFormatType()));
|
||||
clipboard.ReadHTML(&markup_result, &url_result);
|
||||
EXPECT_EQ(markup, markup_result);
|
||||
#if defined(OS_WIN)
|
||||
// TODO(playmobil): It's not clear that non windows clipboards need to support
|
||||
// this.
|
||||
EXPECT_EQ(url, url_result);
|
||||
#endif // defined(OS_WIN)
|
||||
}
|
||||
|
||||
// TODO(estade): Port the following test (decide what target we use for urls)
|
||||
#if !defined(OS_LINUX)
|
||||
TEST_F(ClipboardTest, BookmarkTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
string16 title(ASCIIToUTF16("The Example Company")), title_result;
|
||||
std::string url("http://www.example.com/"), url_result;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteBookmark(title, url);
|
||||
}
|
||||
|
||||
EXPECT_EQ(true,
|
||||
clipboard.IsFormatAvailable(Clipboard::GetUrlWFormatType()));
|
||||
clipboard.ReadBookmark(&title_result, &url_result);
|
||||
EXPECT_EQ(title, title_result);
|
||||
EXPECT_EQ(url, url_result);
|
||||
}
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
TEST_F(ClipboardTest, MultiFormatTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
string16 text(ASCIIToUTF16("Hi!")), text_result;
|
||||
string16 markup(ASCIIToUTF16("<strong>Hi!</string>")), markup_result;
|
||||
std::string url("http://www.example.com/"), url_result;
|
||||
std::string ascii_text;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteHTML(markup, url);
|
||||
clipboard_writer.WriteText(text);
|
||||
}
|
||||
|
||||
EXPECT_EQ(true,
|
||||
clipboard.IsFormatAvailable(Clipboard::GetHtmlFormatType()));
|
||||
EXPECT_EQ(true, clipboard.IsFormatAvailable(
|
||||
Clipboard::GetPlainTextWFormatType()));
|
||||
EXPECT_EQ(true, clipboard.IsFormatAvailable(
|
||||
Clipboard::GetPlainTextFormatType()));
|
||||
clipboard.ReadHTML(&markup_result, &url_result);
|
||||
EXPECT_EQ(markup, markup_result);
|
||||
#if defined(OS_WIN)
|
||||
// TODO(playmobil): It's not clear that non windows clipboards need to support
|
||||
// this.
|
||||
EXPECT_EQ(url, url_result);
|
||||
#endif // defined(OS_WIN)
|
||||
clipboard.ReadText(&text_result);
|
||||
EXPECT_EQ(text, text_result);
|
||||
clipboard.ReadAsciiText(&ascii_text);
|
||||
EXPECT_EQ(UTF16ToUTF8(text), ascii_text);
|
||||
}
|
||||
|
||||
// TODO(estade): Port the following tests (decide what targets we use for files)
|
||||
#if !defined(OS_LINUX)
|
||||
// Files for this test don't actually need to exist on the file system, just
|
||||
// don't try to use a non-existent file you've retrieved from the clipboard.
|
||||
TEST_F(ClipboardTest, FileTest) {
|
||||
Clipboard clipboard;
|
||||
#if defined(OS_WIN)
|
||||
FilePath file(L"C:\\Downloads\\My Downloads\\A Special File.txt");
|
||||
#elif defined(OS_MACOSX)
|
||||
// OS X will print a warning message if we stick a non-existant file on the
|
||||
// clipboard.
|
||||
FilePath file("/usr/bin/make");
|
||||
#endif // defined(OS_MACOSX)
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteFile(file);
|
||||
}
|
||||
|
||||
FilePath out_file;
|
||||
clipboard.ReadFile(&out_file);
|
||||
EXPECT_EQ(file.value(), out_file.value());
|
||||
}
|
||||
|
||||
TEST_F(ClipboardTest, MultipleFilesTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
#if defined(OS_WIN)
|
||||
FilePath file1(L"C:\\Downloads\\My Downloads\\File 1.exe");
|
||||
FilePath file2(L"C:\\Downloads\\My Downloads\\File 2.pdf");
|
||||
FilePath file3(L"C:\\Downloads\\My Downloads\\File 3.doc");
|
||||
#elif defined(OS_MACOSX)
|
||||
// OS X will print a warning message if we stick a non-existant file on the
|
||||
// clipboard.
|
||||
FilePath file1("/usr/bin/make");
|
||||
FilePath file2("/usr/bin/man");
|
||||
FilePath file3("/usr/bin/perl");
|
||||
#endif // defined(OS_MACOSX)
|
||||
std::vector<FilePath> files;
|
||||
files.push_back(file1);
|
||||
files.push_back(file2);
|
||||
files.push_back(file3);
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteFiles(files);
|
||||
}
|
||||
|
||||
std::vector<FilePath> out_files;
|
||||
clipboard.ReadFiles(&out_files);
|
||||
|
||||
EXPECT_EQ(files.size(), out_files.size());
|
||||
for (size_t i = 0; i < out_files.size(); ++i)
|
||||
EXPECT_EQ(files[i].value(), out_files[i].value());
|
||||
}
|
||||
#endif // !defined(OS_LINUX)
|
||||
|
||||
#if defined(OS_WIN) // Windows only tests.
|
||||
TEST_F(ClipboardTest, HyperlinkTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
string16 title(ASCIIToUTF16("The Example Company")), title_result;
|
||||
std::string url("http://www.example.com/"), url_result;
|
||||
string16 html(ASCIIToUTF16("<a href=\"http://www.example.com/\">"
|
||||
"The Example Company</a>")), html_result;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteHyperlink(title, url);
|
||||
}
|
||||
|
||||
EXPECT_EQ(true,
|
||||
clipboard.IsFormatAvailable(Clipboard::GetUrlWFormatType()));
|
||||
EXPECT_EQ(true,
|
||||
clipboard.IsFormatAvailable(Clipboard::GetHtmlFormatType()));
|
||||
clipboard.ReadBookmark(&title_result, &url_result);
|
||||
EXPECT_EQ(title, title_result);
|
||||
EXPECT_EQ(url, url_result);
|
||||
clipboard.ReadHTML(&html_result, &url_result);
|
||||
EXPECT_EQ(html, html_result);
|
||||
}
|
||||
|
||||
TEST_F(ClipboardTest, WebSmartPasteTest) {
|
||||
Clipboard clipboard;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteWebSmartPaste();
|
||||
}
|
||||
|
||||
EXPECT_EQ(true, clipboard.IsFormatAvailable(
|
||||
Clipboard::GetWebKitSmartPasteFormatType()));
|
||||
}
|
||||
|
||||
TEST_F(ClipboardTest, BitmapTest) {
|
||||
unsigned int fake_bitmap[] = {
|
||||
0x46155189, 0xF6A55C8D, 0x79845674, 0xFA57BD89,
|
||||
0x78FD46AE, 0x87C64F5A, 0x36EDC5AF, 0x4378F568,
|
||||
0x91E9F63A, 0xC31EA14F, 0x69AB32DF, 0x643A3FD1,
|
||||
};
|
||||
|
||||
Clipboard clipboard;
|
||||
|
||||
{
|
||||
ScopedClipboardWriter clipboard_writer(&clipboard);
|
||||
clipboard_writer.WriteBitmapFromPixels(fake_bitmap, gfx::Size(3, 4));
|
||||
}
|
||||
|
||||
EXPECT_EQ(true, clipboard.IsFormatAvailable(
|
||||
Clipboard::GetBitmapFormatType()));
|
||||
}
|
||||
#endif // defined(OS_WIN)
|
@ -1,488 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/clipboard_util.h"
|
||||
|
||||
#include <shellapi.h>
|
||||
#include <shlwapi.h>
|
||||
#include <wininet.h>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/scoped_handle.h"
|
||||
#include "base/string_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
bool GetUrlFromHDrop(IDataObject* data_object, std::wstring* url,
|
||||
std::wstring* title) {
|
||||
DCHECK(data_object && url && title);
|
||||
|
||||
STGMEDIUM medium;
|
||||
if (FAILED(data_object->GetData(ClipboardUtil::GetCFHDropFormat(), &medium)))
|
||||
return false;
|
||||
|
||||
HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal));
|
||||
|
||||
if (!hdrop)
|
||||
return false;
|
||||
|
||||
bool success = false;
|
||||
wchar_t filename[MAX_PATH];
|
||||
if (DragQueryFileW(hdrop, 0, filename, arraysize(filename))) {
|
||||
wchar_t url_buffer[INTERNET_MAX_URL_LENGTH];
|
||||
if (0 == _wcsicmp(PathFindExtensionW(filename), L".url") &&
|
||||
GetPrivateProfileStringW(L"InternetShortcut", L"url", 0, url_buffer,
|
||||
arraysize(url_buffer), filename)) {
|
||||
*url = url_buffer;
|
||||
PathRemoveExtension(filename);
|
||||
title->assign(PathFindFileName(filename));
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
DragFinish(hdrop);
|
||||
GlobalUnlock(medium.hGlobal);
|
||||
// We don't need to call ReleaseStgMedium here because as far as I can tell,
|
||||
// DragFinish frees the hGlobal for us.
|
||||
return success;
|
||||
}
|
||||
|
||||
bool SplitUrlAndTitle(const std::wstring& str, std::wstring* url,
|
||||
std::wstring* title) {
|
||||
DCHECK(url && title);
|
||||
size_t newline_pos = str.find('\n');
|
||||
bool success = false;
|
||||
if (newline_pos != std::string::npos) {
|
||||
*url = str.substr(0, newline_pos);
|
||||
title->assign(str.substr(newline_pos + 1));
|
||||
success = true;
|
||||
} else {
|
||||
*url = str;
|
||||
title->assign(str);
|
||||
success = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
FORMATETC* ClipboardUtil::GetUrlFormat() {
|
||||
static UINT cf = RegisterClipboardFormat(CFSTR_INETURLA);
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetUrlWFormat() {
|
||||
static UINT cf = RegisterClipboardFormat(CFSTR_INETURLW);
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetMozUrlFormat() {
|
||||
// The format is "URL\nTitle"
|
||||
static UINT cf = RegisterClipboardFormat(L"text/x-moz-url");
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetPlainTextFormat() {
|
||||
// We don't need to register this format since it's a built in format.
|
||||
static FORMATETC format = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetPlainTextWFormat() {
|
||||
// We don't need to register this format since it's a built in format.
|
||||
static FORMATETC format = {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1,
|
||||
TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetFilenameWFormat() {
|
||||
static UINT cf = RegisterClipboardFormat(CFSTR_FILENAMEW);
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetFilenameFormat()
|
||||
{
|
||||
static UINT cf = RegisterClipboardFormat(CFSTR_FILENAMEA);
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetHtmlFormat() {
|
||||
static UINT cf = RegisterClipboardFormat(L"HTML Format");
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetTextHtmlFormat() {
|
||||
static UINT cf = RegisterClipboardFormat(L"text/html");
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetCFHDropFormat() {
|
||||
// We don't need to register this format since it's a built in format.
|
||||
static FORMATETC format = {CF_HDROP, 0, DVASPECT_CONTENT, -1,
|
||||
TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetFileDescriptorFormat() {
|
||||
static UINT cf = RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR);
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetFileContentFormatZero() {
|
||||
static UINT cf = RegisterClipboardFormat(CFSTR_FILECONTENTS);
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
FORMATETC* ClipboardUtil::GetWebKitSmartPasteFormat() {
|
||||
static UINT cf = RegisterClipboardFormat(L"WebKit Smart Paste Format");
|
||||
static FORMATETC format = {cf, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL};
|
||||
return &format;
|
||||
}
|
||||
|
||||
|
||||
bool ClipboardUtil::HasUrl(IDataObject* data_object) {
|
||||
DCHECK(data_object);
|
||||
return SUCCEEDED(data_object->QueryGetData(GetMozUrlFormat())) ||
|
||||
SUCCEEDED(data_object->QueryGetData(GetUrlWFormat())) ||
|
||||
SUCCEEDED(data_object->QueryGetData(GetUrlFormat())) ||
|
||||
SUCCEEDED(data_object->QueryGetData(GetFilenameWFormat())) ||
|
||||
SUCCEEDED(data_object->QueryGetData(GetFilenameFormat()));
|
||||
}
|
||||
|
||||
bool ClipboardUtil::HasFilenames(IDataObject* data_object) {
|
||||
DCHECK(data_object);
|
||||
return SUCCEEDED(data_object->QueryGetData(GetCFHDropFormat()));
|
||||
}
|
||||
|
||||
bool ClipboardUtil::HasPlainText(IDataObject* data_object) {
|
||||
DCHECK(data_object);
|
||||
return SUCCEEDED(data_object->QueryGetData(GetPlainTextWFormat())) ||
|
||||
SUCCEEDED(data_object->QueryGetData(GetPlainTextFormat()));
|
||||
}
|
||||
|
||||
|
||||
bool ClipboardUtil::GetUrl(IDataObject* data_object,
|
||||
std::wstring* url, std::wstring* title) {
|
||||
DCHECK(data_object && url && title);
|
||||
if (!HasUrl(data_object))
|
||||
return false;
|
||||
|
||||
// Try to extract a URL from |data_object| in a variety of formats.
|
||||
STGMEDIUM store;
|
||||
if (GetUrlFromHDrop(data_object, url, title)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(data_object->GetData(GetMozUrlFormat(), &store)) ||
|
||||
SUCCEEDED(data_object->GetData(GetUrlWFormat(), &store))) {
|
||||
// Mozilla URL format or unicode URL
|
||||
ScopedHGlobal<wchar_t> data(store.hGlobal);
|
||||
bool success = SplitUrlAndTitle(data.get(), url, title);
|
||||
ReleaseStgMedium(&store);
|
||||
if (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(data_object->GetData(GetUrlFormat(), &store))) {
|
||||
// URL using ascii
|
||||
ScopedHGlobal<char> data(store.hGlobal);
|
||||
bool success = SplitUrlAndTitle(UTF8ToWide(data.get()), url, title);
|
||||
ReleaseStgMedium(&store);
|
||||
if (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(data_object->GetData(GetFilenameWFormat(), &store))) {
|
||||
// filename using unicode
|
||||
ScopedHGlobal<wchar_t> data(store.hGlobal);
|
||||
bool success = false;
|
||||
if (data.get() && data.get()[0] && (PathFileExists(data.get()) ||
|
||||
PathIsUNC(data.get()))) {
|
||||
wchar_t file_url[INTERNET_MAX_URL_LENGTH];
|
||||
DWORD file_url_len = sizeof(file_url) / sizeof(file_url[0]);
|
||||
if (SUCCEEDED(::UrlCreateFromPathW(data.get(), file_url, &file_url_len,
|
||||
0))) {
|
||||
*url = file_url;
|
||||
title->assign(file_url);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
ReleaseStgMedium(&store);
|
||||
if (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SUCCEEDED(data_object->GetData(GetFilenameFormat(), &store))) {
|
||||
// filename using ascii
|
||||
ScopedHGlobal<char> data(store.hGlobal);
|
||||
bool success = false;
|
||||
if (data.get() && data.get()[0] && (PathFileExistsA(data.get()) ||
|
||||
PathIsUNCA(data.get()))) {
|
||||
char file_url[INTERNET_MAX_URL_LENGTH];
|
||||
DWORD file_url_len = sizeof(file_url) / sizeof(file_url[0]);
|
||||
if (SUCCEEDED(::UrlCreateFromPathA(data.get(),
|
||||
file_url,
|
||||
&file_url_len,
|
||||
0))) {
|
||||
*url = UTF8ToWide(file_url);
|
||||
title->assign(*url);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
ReleaseStgMedium(&store);
|
||||
if (success)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ClipboardUtil::GetFilenames(IDataObject* data_object,
|
||||
std::vector<std::wstring>* filenames) {
|
||||
DCHECK(data_object && filenames);
|
||||
if (!HasFilenames(data_object))
|
||||
return false;
|
||||
|
||||
STGMEDIUM medium;
|
||||
if (FAILED(data_object->GetData(GetCFHDropFormat(), &medium)))
|
||||
return false;
|
||||
|
||||
HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal));
|
||||
if (!hdrop)
|
||||
return false;
|
||||
|
||||
const int kMaxFilenameLen = 4096;
|
||||
const unsigned num_files = DragQueryFileW(hdrop, 0xffffffff, 0, 0);
|
||||
for (unsigned int i = 0; i < num_files; ++i) {
|
||||
wchar_t filename[kMaxFilenameLen];
|
||||
if (!DragQueryFileW(hdrop, i, filename, kMaxFilenameLen))
|
||||
continue;
|
||||
filenames->push_back(filename);
|
||||
}
|
||||
|
||||
DragFinish(hdrop);
|
||||
GlobalUnlock(medium.hGlobal);
|
||||
// We don't need to call ReleaseStgMedium here because as far as I can tell,
|
||||
// DragFinish frees the hGlobal for us.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClipboardUtil::GetPlainText(IDataObject* data_object,
|
||||
std::wstring* plain_text) {
|
||||
DCHECK(data_object && plain_text);
|
||||
if (!HasPlainText(data_object))
|
||||
return false;
|
||||
|
||||
STGMEDIUM store;
|
||||
bool success = false;
|
||||
if (SUCCEEDED(data_object->GetData(GetPlainTextWFormat(), &store))) {
|
||||
// Unicode text
|
||||
ScopedHGlobal<wchar_t> data(store.hGlobal);
|
||||
plain_text->assign(data.get());
|
||||
ReleaseStgMedium(&store);
|
||||
success = true;
|
||||
} else if (SUCCEEDED(data_object->GetData(GetPlainTextFormat(), &store))) {
|
||||
// ascii text
|
||||
ScopedHGlobal<char> data(store.hGlobal);
|
||||
plain_text->assign(UTF8ToWide(data.get()));
|
||||
ReleaseStgMedium(&store);
|
||||
success = true;
|
||||
} else {
|
||||
//If a file is dropped on the window, it does not provide either of the
|
||||
//plain text formats, so here we try to forcibly get a url.
|
||||
std::wstring title;
|
||||
success = GetUrl(data_object, plain_text, &title);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ClipboardUtil::GetHtml(IDataObject* data_object,
|
||||
std::wstring* html, std::string* base_url) {
|
||||
DCHECK(data_object && html && base_url);
|
||||
|
||||
if (SUCCEEDED(data_object->QueryGetData(GetHtmlFormat()))) {
|
||||
STGMEDIUM store;
|
||||
if (SUCCEEDED(data_object->GetData(GetHtmlFormat(), &store))) {
|
||||
// MS CF html
|
||||
ScopedHGlobal<char> data(store.hGlobal);
|
||||
|
||||
std::string html_utf8;
|
||||
CFHtmlToHtml(std::string(data.get(), data.Size()), &html_utf8, base_url);
|
||||
html->assign(UTF8ToWide(html_utf8));
|
||||
|
||||
ReleaseStgMedium(&store);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (FAILED(data_object->QueryGetData(GetTextHtmlFormat())))
|
||||
return false;
|
||||
|
||||
STGMEDIUM store;
|
||||
if (FAILED(data_object->GetData(GetTextHtmlFormat(), &store)))
|
||||
return false;
|
||||
|
||||
// text/html
|
||||
ScopedHGlobal<wchar_t> data(store.hGlobal);
|
||||
html->assign(data.get());
|
||||
ReleaseStgMedium(&store);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClipboardUtil::GetFileContents(IDataObject* data_object,
|
||||
std::wstring* filename, std::string* file_contents) {
|
||||
DCHECK(data_object && filename && file_contents);
|
||||
bool has_data =
|
||||
SUCCEEDED(data_object->QueryGetData(GetFileContentFormatZero())) ||
|
||||
SUCCEEDED(data_object->QueryGetData(GetFileDescriptorFormat()));
|
||||
|
||||
if (!has_data)
|
||||
return false;
|
||||
|
||||
STGMEDIUM content;
|
||||
// The call to GetData can be very slow depending on what is in
|
||||
// |data_object|.
|
||||
if (SUCCEEDED(data_object->GetData(GetFileContentFormatZero(), &content))) {
|
||||
if (TYMED_HGLOBAL == content.tymed) {
|
||||
ScopedHGlobal<char> data(content.hGlobal);
|
||||
file_contents->assign(data.get(), data.Size());
|
||||
}
|
||||
ReleaseStgMedium(&content);
|
||||
}
|
||||
|
||||
STGMEDIUM description;
|
||||
if (SUCCEEDED(data_object->GetData(GetFileDescriptorFormat(),
|
||||
&description))) {
|
||||
ScopedHGlobal<FILEGROUPDESCRIPTOR> fgd(description.hGlobal);
|
||||
// We expect there to be at least one file in here.
|
||||
DCHECK(fgd->cItems >= 1);
|
||||
filename->assign(fgd->fgd[0].cFileName);
|
||||
ReleaseStgMedium(&description);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// HtmlToCFHtml and CFHtmlToHtml are based on similar methods in
|
||||
// WebCore/platform/win/ClipboardUtilitiesWin.cpp.
|
||||
/*
|
||||
* Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// Helper method for converting from text/html to MS CF_HTML.
|
||||
// Documentation for the CF_HTML format is available at
|
||||
// http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx
|
||||
std::string ClipboardUtil::HtmlToCFHtml(const std::string& html,
|
||||
const std::string& base_url) {
|
||||
if (html.empty())
|
||||
return std::string();
|
||||
|
||||
#define MAX_DIGITS 10
|
||||
#define MAKE_NUMBER_FORMAT_1(digits) MAKE_NUMBER_FORMAT_2(digits)
|
||||
#define MAKE_NUMBER_FORMAT_2(digits) "%0" #digits "u"
|
||||
#define NUMBER_FORMAT MAKE_NUMBER_FORMAT_1(MAX_DIGITS)
|
||||
|
||||
static const char* header = "Version:0.9\r\n"
|
||||
"StartHTML:" NUMBER_FORMAT "\r\n"
|
||||
"EndHTML:" NUMBER_FORMAT "\r\n"
|
||||
"StartFragment:" NUMBER_FORMAT "\r\n"
|
||||
"EndFragment:" NUMBER_FORMAT "\r\n";
|
||||
static const char* source_url_prefix = "SourceURL:";
|
||||
|
||||
static const char* start_markup =
|
||||
"<html>\r\n<body>\r\n<!--StartFragment-->\r\n";
|
||||
static const char* end_markup =
|
||||
"\r\n<!--EndFragment-->\r\n</body>\r\n</html>";
|
||||
|
||||
// Calculate offsets
|
||||
size_t start_html_offset = strlen(header) - strlen(NUMBER_FORMAT) * 4 +
|
||||
MAX_DIGITS * 4;
|
||||
if (!base_url.empty()) {
|
||||
start_html_offset += strlen(source_url_prefix) +
|
||||
base_url.length() + 2; // Add 2 for \r\n.
|
||||
}
|
||||
size_t start_fragment_offset = start_html_offset + strlen(start_markup);
|
||||
size_t end_fragment_offset = start_fragment_offset + html.length();
|
||||
size_t end_html_offset = end_fragment_offset + strlen(end_markup);
|
||||
|
||||
std::string result = StringPrintf(header, start_html_offset,
|
||||
end_html_offset, start_fragment_offset, end_fragment_offset);
|
||||
if (!base_url.empty()) {
|
||||
result.append(source_url_prefix);
|
||||
result.append(base_url);
|
||||
result.append("\r\n");
|
||||
}
|
||||
result.append(start_markup);
|
||||
result.append(html);
|
||||
result.append(end_markup);
|
||||
|
||||
#undef MAX_DIGITS
|
||||
#undef MAKE_NUMBER_FORMAT_1
|
||||
#undef MAKE_NUMBER_FORMAT_2
|
||||
#undef NUMBER_FORMAT
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper method for converting from MS CF_HTML to text/html.
|
||||
void ClipboardUtil::CFHtmlToHtml(const std::string& cf_html,
|
||||
std::string* html,
|
||||
std::string* base_url) {
|
||||
// Obtain base_url if present.
|
||||
static std::string src_url_str("SourceURL:");
|
||||
size_t line_start = cf_html.find(src_url_str);
|
||||
if (line_start != std::string::npos) {
|
||||
size_t src_end = cf_html.find("\n", line_start);
|
||||
size_t src_start = line_start + src_url_str.length();
|
||||
if (src_end != std::string::npos && src_start != std::string::npos) {
|
||||
*base_url = cf_html.substr(src_start, src_end - src_start);
|
||||
TrimWhitespace(*base_url, TRIM_ALL, base_url);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the markup between "<!--StartFragment -->" and "<!--EndFragment-->".
|
||||
std::string cf_html_lower = StringToLowerASCII(cf_html);
|
||||
size_t markup_start = cf_html_lower.find("<html", 0);
|
||||
size_t tag_start = cf_html.find("StartFragment", markup_start);
|
||||
size_t fragment_start = cf_html.find('>', tag_start) + 1;
|
||||
size_t tag_end = cf_html.rfind("EndFragment", std::string::npos);
|
||||
size_t fragment_end = cf_html.rfind('<', tag_end);
|
||||
if (fragment_start != std::string::npos &&
|
||||
fragment_end != std::string::npos) {
|
||||
*html = cf_html.substr(fragment_start, fragment_end - fragment_start);
|
||||
TrimWhitespace(*html, TRIM_ALL, html);
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
//
|
||||
// Some helper functions for working with the clipboard and IDataObjects.
|
||||
|
||||
#ifndef BASE_CLIPBOARD_UTIL_H_
|
||||
#define BASE_CLIPBOARD_UTIL_H_
|
||||
|
||||
#include <shlobj.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class ClipboardUtil {
|
||||
public:
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Clipboard formats.
|
||||
static FORMATETC* GetUrlFormat();
|
||||
static FORMATETC* GetUrlWFormat();
|
||||
static FORMATETC* GetMozUrlFormat();
|
||||
static FORMATETC* GetPlainTextFormat();
|
||||
static FORMATETC* GetPlainTextWFormat();
|
||||
static FORMATETC* GetFilenameFormat();
|
||||
static FORMATETC* GetFilenameWFormat();
|
||||
// MS HTML Format
|
||||
static FORMATETC* GetHtmlFormat();
|
||||
// Firefox text/html
|
||||
static FORMATETC* GetTextHtmlFormat();
|
||||
static FORMATETC* GetCFHDropFormat();
|
||||
static FORMATETC* GetFileDescriptorFormat();
|
||||
static FORMATETC* GetFileContentFormatZero();
|
||||
static FORMATETC* GetWebKitSmartPasteFormat();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// These methods check to see if |data_object| has the requested type.
|
||||
// Returns true if it does.
|
||||
static bool HasUrl(IDataObject* data_object);
|
||||
static bool HasFilenames(IDataObject* data_object);
|
||||
static bool HasPlainText(IDataObject* data_object);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Helper methods to extract information from an IDataObject. These methods
|
||||
// return true if the requested data type is found in |data_object|.
|
||||
static bool GetUrl(IDataObject* data_object,
|
||||
std::wstring* url, std::wstring* title);
|
||||
static bool GetFilenames(IDataObject* data_object,
|
||||
std::vector<std::wstring>* filenames);
|
||||
static bool GetPlainText(IDataObject* data_object, std::wstring* plain_text);
|
||||
static bool GetHtml(IDataObject* data_object, std::wstring* text_html,
|
||||
std::string* base_url);
|
||||
static bool GetFileContents(IDataObject* data_object,
|
||||
std::wstring* filename,
|
||||
std::string* file_contents);
|
||||
|
||||
// A helper method for converting between MS CF_HTML format and plain
|
||||
// text/html.
|
||||
static std::string HtmlToCFHtml(const std::string& html,
|
||||
const std::string& base_url);
|
||||
static void CFHtmlToHtml(const std::string& cf_html, std::string* html,
|
||||
std::string* base_url);
|
||||
};
|
||||
|
||||
#endif // BASE_CLIPBOARD_UTIL_H_
|
@ -1,650 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Many of these functions are based on those found in
|
||||
// webkit/port/platform/PasteboardWin.cpp
|
||||
|
||||
#include "base/clipboard.h"
|
||||
|
||||
#include <shlobj.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include "base/clipboard_util.h"
|
||||
#include "base/lock.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/message_loop.h"
|
||||
#include "base/shared_memory.h"
|
||||
#include "base/string_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// A scoper to manage acquiring and automatically releasing the clipboard.
|
||||
class ScopedClipboard {
|
||||
public:
|
||||
ScopedClipboard() : opened_(false) { }
|
||||
|
||||
~ScopedClipboard() {
|
||||
if (opened_)
|
||||
Release();
|
||||
}
|
||||
|
||||
bool Acquire(HWND owner) {
|
||||
const int kMaxAttemptsToOpenClipboard = 5;
|
||||
|
||||
if (opened_) {
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt to open the clipboard, which will acquire the Windows clipboard
|
||||
// lock. This may fail if another process currently holds this lock.
|
||||
// We're willing to try a few times in the hopes of acquiring it.
|
||||
//
|
||||
// This turns out to be an issue when using remote desktop because the
|
||||
// rdpclip.exe process likes to read what we've written to the clipboard and
|
||||
// send it to the RDP client. If we open and close the clipboard in quick
|
||||
// succession, we might be trying to open it while rdpclip.exe has it open,
|
||||
// See Bug 815425.
|
||||
//
|
||||
// In fact, we believe we'll only spin this loop over remote desktop. In
|
||||
// normal situations, the user is initiating clipboard operations and there
|
||||
// shouldn't be contention.
|
||||
|
||||
for (int attempts = 0; attempts < kMaxAttemptsToOpenClipboard; ++attempts) {
|
||||
// If we didn't manage to open the clipboard, sleep a bit and be hopeful.
|
||||
if (attempts != 0)
|
||||
::Sleep(5);
|
||||
|
||||
if (::OpenClipboard(owner)) {
|
||||
opened_ = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We failed to acquire the clipboard.
|
||||
return false;
|
||||
}
|
||||
|
||||
void Release() {
|
||||
if (opened_) {
|
||||
::CloseClipboard();
|
||||
opened_ = false;
|
||||
} else {
|
||||
NOTREACHED();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool opened_;
|
||||
};
|
||||
|
||||
LRESULT CALLBACK ClipboardOwnerWndProc(HWND hwnd,
|
||||
UINT message,
|
||||
WPARAM wparam,
|
||||
LPARAM lparam) {
|
||||
LRESULT lresult = 0;
|
||||
|
||||
switch (message) {
|
||||
case WM_RENDERFORMAT:
|
||||
// This message comes when SetClipboardData was sent a null data handle
|
||||
// and now it's come time to put the data on the clipboard.
|
||||
// We always set data, so there isn't a need to actually do anything here.
|
||||
break;
|
||||
case WM_RENDERALLFORMATS:
|
||||
// This message comes when SetClipboardData was sent a null data handle
|
||||
// and now this application is about to quit, so it must put data on
|
||||
// the clipboard before it exits.
|
||||
// We always set data, so there isn't a need to actually do anything here.
|
||||
break;
|
||||
case WM_DRAWCLIPBOARD:
|
||||
break;
|
||||
case WM_DESTROY:
|
||||
break;
|
||||
case WM_CHANGECBCHAIN:
|
||||
break;
|
||||
default:
|
||||
lresult = DefWindowProc(hwnd, message, wparam, lparam);
|
||||
break;
|
||||
}
|
||||
return lresult;
|
||||
}
|
||||
|
||||
template <typename charT>
|
||||
HGLOBAL CreateGlobalData(const std::basic_string<charT>& str) {
|
||||
HGLOBAL data =
|
||||
::GlobalAlloc(GMEM_MOVEABLE, ((str.size() + 1) * sizeof(charT)));
|
||||
if (data) {
|
||||
charT* raw_data = static_cast<charT*>(::GlobalLock(data));
|
||||
memcpy(raw_data, str.data(), str.size() * sizeof(charT));
|
||||
raw_data[str.size()] = '\0';
|
||||
::GlobalUnlock(data);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Clipboard::Clipboard() : create_window_(false) {
|
||||
if (MessageLoop::current()->type() == MessageLoop::TYPE_UI) {
|
||||
// Make a dummy HWND to be the clipboard's owner.
|
||||
WNDCLASSEX wcex = {0};
|
||||
wcex.cbSize = sizeof(WNDCLASSEX);
|
||||
wcex.lpfnWndProc = ClipboardOwnerWndProc;
|
||||
wcex.hInstance = GetModuleHandle(NULL);
|
||||
wcex.lpszClassName = L"ClipboardOwnerWindowClass";
|
||||
::RegisterClassEx(&wcex);
|
||||
create_window_ = true;
|
||||
}
|
||||
|
||||
clipboard_owner_ = NULL;
|
||||
}
|
||||
|
||||
Clipboard::~Clipboard() {
|
||||
if (clipboard_owner_)
|
||||
::DestroyWindow(clipboard_owner_);
|
||||
clipboard_owner_ = NULL;
|
||||
}
|
||||
|
||||
void Clipboard::WriteObjects(const ObjectMap& objects) {
|
||||
WriteObjects(objects, NULL);
|
||||
}
|
||||
|
||||
void Clipboard::WriteObjects(const ObjectMap& objects,
|
||||
base::ProcessHandle process) {
|
||||
ScopedClipboard clipboard;
|
||||
if (!clipboard.Acquire(GetClipboardWindow()))
|
||||
return;
|
||||
|
||||
::EmptyClipboard();
|
||||
|
||||
for (ObjectMap::const_iterator iter = objects.begin();
|
||||
iter != objects.end(); ++iter) {
|
||||
if (iter->first == CBF_SMBITMAP)
|
||||
WriteBitmapFromSharedMemory(&(iter->second[0].front()),
|
||||
&(iter->second[1].front()),
|
||||
process);
|
||||
else
|
||||
DispatchObject(static_cast<ObjectType>(iter->first), iter->second);
|
||||
}
|
||||
}
|
||||
|
||||
void Clipboard::WriteText(const char* text_data, size_t text_len) {
|
||||
string16 text;
|
||||
UTF8ToUTF16(text_data, text_len, &text);
|
||||
HGLOBAL glob = CreateGlobalData(text);
|
||||
|
||||
WriteToClipboard(CF_UNICODETEXT, glob);
|
||||
}
|
||||
|
||||
void Clipboard::WriteHTML(const char* markup_data,
|
||||
size_t markup_len,
|
||||
const char* url_data,
|
||||
size_t url_len) {
|
||||
std::string markup(markup_data, markup_len);
|
||||
std::string url;
|
||||
|
||||
if (url_len > 0)
|
||||
url.assign(url_data, url_len);
|
||||
|
||||
std::string html_fragment = ClipboardUtil::HtmlToCFHtml(markup, url);
|
||||
HGLOBAL glob = CreateGlobalData(html_fragment);
|
||||
|
||||
WriteToClipboard(StringToInt(GetHtmlFormatType()), glob);
|
||||
}
|
||||
|
||||
void Clipboard::WriteBookmark(const char* title_data,
|
||||
size_t title_len,
|
||||
const char* url_data,
|
||||
size_t url_len) {
|
||||
std::string bookmark(title_data, title_len);
|
||||
bookmark.append(1, L'\n');
|
||||
bookmark.append(url_data, url_len);
|
||||
|
||||
string16 wide_bookmark = UTF8ToWide(bookmark);
|
||||
HGLOBAL glob = CreateGlobalData(wide_bookmark);
|
||||
|
||||
WriteToClipboard(StringToInt(GetUrlWFormatType()), glob);
|
||||
}
|
||||
|
||||
void Clipboard::WriteHyperlink(const char* title_data,
|
||||
size_t title_len,
|
||||
const char* url_data,
|
||||
size_t url_len) {
|
||||
// Store as a bookmark.
|
||||
WriteBookmark(title_data, title_len, url_data, url_len);
|
||||
|
||||
std::string title(title_data, title_len),
|
||||
url(url_data, url_len),
|
||||
link("<a href=\"");
|
||||
|
||||
// Construct the hyperlink.
|
||||
link.append(url);
|
||||
link.append("\">");
|
||||
link.append(title);
|
||||
link.append("</a>");
|
||||
|
||||
// Store hyperlink as html.
|
||||
WriteHTML(link.c_str(), link.size(), NULL, 0);
|
||||
}
|
||||
|
||||
void Clipboard::WriteWebSmartPaste() {
|
||||
DCHECK(clipboard_owner_);
|
||||
::SetClipboardData(StringToInt(GetWebKitSmartPasteFormatType()), NULL);
|
||||
}
|
||||
|
||||
void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) {
|
||||
const gfx::Size* size = reinterpret_cast<const gfx::Size*>(size_data);
|
||||
HDC dc = ::GetDC(NULL);
|
||||
|
||||
// This doesn't actually cost us a memcpy when the bitmap comes from the
|
||||
// renderer as we load it into the bitmap using setPixels which just sets a
|
||||
// pointer. Someone has to memcpy it into GDI, it might as well be us here.
|
||||
|
||||
// TODO(darin): share data in gfx/bitmap_header.cc somehow
|
||||
BITMAPINFO bm_info = {0};
|
||||
bm_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bm_info.bmiHeader.biWidth = size->width();
|
||||
bm_info.bmiHeader.biHeight = -size->height(); // sets vertical orientation
|
||||
bm_info.bmiHeader.biPlanes = 1;
|
||||
bm_info.bmiHeader.biBitCount = 32;
|
||||
bm_info.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
// ::CreateDIBSection allocates memory for us to copy our bitmap into.
|
||||
// Unfortunately, we can't write the created bitmap to the clipboard,
|
||||
// (see http://msdn2.microsoft.com/en-us/library/ms532292.aspx)
|
||||
void *bits;
|
||||
HBITMAP source_hbitmap =
|
||||
::CreateDIBSection(dc, &bm_info, DIB_RGB_COLORS, &bits, NULL, 0);
|
||||
|
||||
if (bits && source_hbitmap) {
|
||||
// Copy the bitmap out of shared memory and into GDI
|
||||
memcpy(bits, pixel_data, 4 * size->width() * size->height());
|
||||
|
||||
// Now we have an HBITMAP, we can write it to the clipboard
|
||||
WriteBitmapFromHandle(source_hbitmap, *size);
|
||||
}
|
||||
|
||||
::DeleteObject(source_hbitmap);
|
||||
::ReleaseDC(NULL, dc);
|
||||
}
|
||||
|
||||
void Clipboard::WriteBitmapFromSharedMemory(const char* bitmap_data,
|
||||
const char* size_data,
|
||||
base::ProcessHandle process) {
|
||||
const gfx::Size* size = reinterpret_cast<const gfx::Size*>(size_data);
|
||||
|
||||
// bitmap_data has an encoded shared memory object. See
|
||||
// DuplicateRemoteHandles().
|
||||
char* ptr = const_cast<char*>(bitmap_data);
|
||||
scoped_ptr<const base::SharedMemory> bitmap(*
|
||||
reinterpret_cast<const base::SharedMemory**>(ptr));
|
||||
|
||||
// TODO(darin): share data in gfx/bitmap_header.cc somehow.
|
||||
BITMAPINFO bm_info = {0};
|
||||
bm_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bm_info.bmiHeader.biWidth = size->width();
|
||||
// Sets the vertical orientation.
|
||||
bm_info.bmiHeader.biHeight = -size->height();
|
||||
bm_info.bmiHeader.biPlanes = 1;
|
||||
bm_info.bmiHeader.biBitCount = 32;
|
||||
bm_info.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
HDC dc = ::GetDC(NULL);
|
||||
|
||||
// We can create an HBITMAP directly using the shared memory handle, saving
|
||||
// a memcpy.
|
||||
HBITMAP source_hbitmap =
|
||||
::CreateDIBSection(dc, &bm_info, DIB_RGB_COLORS, NULL,
|
||||
bitmap->handle(), 0);
|
||||
|
||||
if (source_hbitmap) {
|
||||
// Now we can write the HBITMAP to the clipboard
|
||||
WriteBitmapFromHandle(source_hbitmap, *size);
|
||||
}
|
||||
|
||||
::DeleteObject(source_hbitmap);
|
||||
::ReleaseDC(NULL, dc);
|
||||
}
|
||||
|
||||
void Clipboard::WriteBitmapFromHandle(HBITMAP source_hbitmap,
|
||||
const gfx::Size& size) {
|
||||
// We would like to just call ::SetClipboardData on the source_hbitmap,
|
||||
// but that bitmap might not be of a sort we can write to the clipboard.
|
||||
// For this reason, we create a new bitmap, copy the bits over, and then
|
||||
// write that to the clipboard.
|
||||
|
||||
HDC dc = ::GetDC(NULL);
|
||||
HDC compatible_dc = ::CreateCompatibleDC(NULL);
|
||||
HDC source_dc = ::CreateCompatibleDC(NULL);
|
||||
|
||||
// This is the HBITMAP we will eventually write to the clipboard
|
||||
HBITMAP hbitmap = ::CreateCompatibleBitmap(dc, size.width(), size.height());
|
||||
if (!hbitmap) {
|
||||
// Failed to create the bitmap
|
||||
::DeleteDC(compatible_dc);
|
||||
::DeleteDC(source_dc);
|
||||
::ReleaseDC(NULL, dc);
|
||||
return;
|
||||
}
|
||||
|
||||
HBITMAP old_hbitmap = (HBITMAP)SelectObject(compatible_dc, hbitmap);
|
||||
HBITMAP old_source = (HBITMAP)SelectObject(source_dc, source_hbitmap);
|
||||
|
||||
// Now we need to blend it into an HBITMAP we can place on the clipboard
|
||||
BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
|
||||
::GdiAlphaBlend(compatible_dc, 0, 0, size.width(), size.height(),
|
||||
source_dc, 0, 0, size.width(), size.height(), bf);
|
||||
|
||||
// Clean up all the handles we just opened
|
||||
::SelectObject(compatible_dc, old_hbitmap);
|
||||
::SelectObject(source_dc, old_source);
|
||||
::DeleteObject(old_hbitmap);
|
||||
::DeleteObject(old_source);
|
||||
::DeleteDC(compatible_dc);
|
||||
::DeleteDC(source_dc);
|
||||
::ReleaseDC(NULL, dc);
|
||||
|
||||
WriteToClipboard(CF_BITMAP, hbitmap);
|
||||
}
|
||||
|
||||
// Write a file or set of files to the clipboard in HDROP format. When the user
|
||||
// invokes a paste command (in a Windows explorer shell, for example), the files
|
||||
// will be copied to the paste location.
|
||||
void Clipboard::WriteFiles(const char* file_data, size_t file_len) {
|
||||
// Calculate the amount of space we'll need store the strings and
|
||||
// a DROPFILES struct.
|
||||
size_t bytes = sizeof(DROPFILES) + file_len;
|
||||
|
||||
HANDLE hdata = ::GlobalAlloc(GMEM_MOVEABLE, bytes);
|
||||
if (!hdata)
|
||||
return;
|
||||
|
||||
char* data = static_cast<char*>(::GlobalLock(hdata));
|
||||
DROPFILES* drop_files = reinterpret_cast<DROPFILES*>(data);
|
||||
drop_files->pFiles = sizeof(DROPFILES);
|
||||
drop_files->fWide = TRUE;
|
||||
|
||||
memcpy(data + sizeof(DROPFILES), file_data, file_len);
|
||||
|
||||
::GlobalUnlock(hdata);
|
||||
WriteToClipboard(CF_HDROP, hdata);
|
||||
}
|
||||
|
||||
void Clipboard::WriteToClipboard(unsigned int format, HANDLE handle) {
|
||||
DCHECK(clipboard_owner_);
|
||||
if (handle && !::SetClipboardData(format, handle)) {
|
||||
DCHECK(ERROR_CLIPBOARD_NOT_OPEN != GetLastError());
|
||||
FreeData(format, handle);
|
||||
}
|
||||
}
|
||||
|
||||
bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format) const {
|
||||
return ::IsClipboardFormatAvailable(StringToInt(format)) != FALSE;
|
||||
}
|
||||
|
||||
void Clipboard::ReadText(string16* result) const {
|
||||
if (!result) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
|
||||
result->clear();
|
||||
|
||||
// Acquire the clipboard.
|
||||
ScopedClipboard clipboard;
|
||||
if (!clipboard.Acquire(GetClipboardWindow()))
|
||||
return;
|
||||
|
||||
HANDLE data = ::GetClipboardData(CF_UNICODETEXT);
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
result->assign(static_cast<const char16*>(::GlobalLock(data)));
|
||||
::GlobalUnlock(data);
|
||||
}
|
||||
|
||||
void Clipboard::ReadAsciiText(std::string* result) const {
|
||||
if (!result) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
|
||||
result->clear();
|
||||
|
||||
// Acquire the clipboard.
|
||||
ScopedClipboard clipboard;
|
||||
if (!clipboard.Acquire(GetClipboardWindow()))
|
||||
return;
|
||||
|
||||
HANDLE data = ::GetClipboardData(CF_TEXT);
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
result->assign(static_cast<const char*>(::GlobalLock(data)));
|
||||
::GlobalUnlock(data);
|
||||
}
|
||||
|
||||
void Clipboard::ReadHTML(string16* markup, std::string* src_url) const {
|
||||
if (markup)
|
||||
markup->clear();
|
||||
|
||||
if (src_url)
|
||||
src_url->clear();
|
||||
|
||||
// Acquire the clipboard.
|
||||
ScopedClipboard clipboard;
|
||||
if (!clipboard.Acquire(GetClipboardWindow()))
|
||||
return;
|
||||
|
||||
HANDLE data = ::GetClipboardData(StringToInt(GetHtmlFormatType()));
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
std::string html_fragment(static_cast<const char*>(::GlobalLock(data)));
|
||||
::GlobalUnlock(data);
|
||||
|
||||
std::string markup_utf8;
|
||||
ClipboardUtil::CFHtmlToHtml(html_fragment, &markup_utf8, src_url);
|
||||
markup->assign(UTF8ToWide(markup_utf8));
|
||||
}
|
||||
|
||||
void Clipboard::ReadBookmark(string16* title, std::string* url) const {
|
||||
if (title)
|
||||
title->clear();
|
||||
|
||||
if (url)
|
||||
url->clear();
|
||||
|
||||
// Acquire the clipboard.
|
||||
ScopedClipboard clipboard;
|
||||
if (!clipboard.Acquire(GetClipboardWindow()))
|
||||
return;
|
||||
|
||||
HANDLE data = ::GetClipboardData(StringToInt(GetUrlWFormatType()));
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
string16 bookmark(static_cast<const char16*>(::GlobalLock(data)));
|
||||
::GlobalUnlock(data);
|
||||
|
||||
ParseBookmarkClipboardFormat(bookmark, title, url);
|
||||
}
|
||||
|
||||
// Read a file in HDROP format from the clipboard.
|
||||
void Clipboard::ReadFile(FilePath* file) const {
|
||||
if (!file) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
|
||||
*file = FilePath();
|
||||
std::vector<FilePath> files;
|
||||
ReadFiles(&files);
|
||||
|
||||
// Take the first file, if available.
|
||||
if (!files.empty())
|
||||
*file = files[0];
|
||||
}
|
||||
|
||||
// Read a set of files in HDROP format from the clipboard.
|
||||
void Clipboard::ReadFiles(std::vector<FilePath>* files) const {
|
||||
if (!files) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
|
||||
files->clear();
|
||||
|
||||
ScopedClipboard clipboard;
|
||||
if (!clipboard.Acquire(GetClipboardWindow()))
|
||||
return;
|
||||
|
||||
HDROP drop = static_cast<HDROP>(::GetClipboardData(CF_HDROP));
|
||||
if (!drop)
|
||||
return;
|
||||
|
||||
// Count of files in the HDROP.
|
||||
int count = ::DragQueryFile(drop, 0xffffffff, NULL, 0);
|
||||
|
||||
if (count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
int size = ::DragQueryFile(drop, i, NULL, 0) + 1;
|
||||
std::wstring file;
|
||||
::DragQueryFile(drop, i, WriteInto(&file, size), size);
|
||||
files->push_back(FilePath(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void Clipboard::ParseBookmarkClipboardFormat(const string16& bookmark,
|
||||
string16* title,
|
||||
std::string* url) {
|
||||
const string16 kDelim = ASCIIToUTF16("\r\n");
|
||||
|
||||
const size_t title_end = bookmark.find_first_of(kDelim);
|
||||
if (title)
|
||||
title->assign(bookmark.substr(0, title_end));
|
||||
|
||||
if (url) {
|
||||
const size_t url_start = bookmark.find_first_not_of(kDelim, title_end);
|
||||
if (url_start != string16::npos)
|
||||
*url = UTF16ToUTF8(bookmark.substr(url_start, string16::npos));
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetUrlFormatType() {
|
||||
return IntToString(ClipboardUtil::GetUrlFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetUrlWFormatType() {
|
||||
return IntToString(ClipboardUtil::GetUrlWFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetMozUrlFormatType() {
|
||||
return IntToString(ClipboardUtil::GetMozUrlFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetPlainTextFormatType() {
|
||||
return IntToString(ClipboardUtil::GetPlainTextFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetPlainTextWFormatType() {
|
||||
return IntToString(ClipboardUtil::GetPlainTextWFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetFilenameFormatType() {
|
||||
return IntToString(ClipboardUtil::GetFilenameFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetFilenameWFormatType() {
|
||||
return IntToString(ClipboardUtil::GetFilenameWFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// MS HTML Format
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetHtmlFormatType() {
|
||||
return IntToString(ClipboardUtil::GetHtmlFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetBitmapFormatType() {
|
||||
return IntToString(CF_BITMAP);
|
||||
}
|
||||
|
||||
// Firefox text/html
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetTextHtmlFormatType() {
|
||||
return IntToString(ClipboardUtil::GetTextHtmlFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetCFHDropFormatType() {
|
||||
return IntToString(ClipboardUtil::GetCFHDropFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetFileDescriptorFormatType() {
|
||||
return IntToString(ClipboardUtil::GetFileDescriptorFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetFileContentFormatZeroType() {
|
||||
return IntToString(ClipboardUtil::GetFileContentFormatZero()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
void Clipboard::DuplicateRemoteHandles(base::ProcessHandle process,
|
||||
ObjectMap* objects) {
|
||||
for (ObjectMap::iterator iter = objects->begin(); iter != objects->end();
|
||||
++iter) {
|
||||
if (iter->first == CBF_SMBITMAP) {
|
||||
// There is a shared memory handle encoded on the first ObjectMapParam.
|
||||
// Use it to open a local handle to the memory.
|
||||
char* bitmap_data = &(iter->second[0].front());
|
||||
base::SharedMemoryHandle* remote_bitmap_handle =
|
||||
reinterpret_cast<base::SharedMemoryHandle*>(bitmap_data);
|
||||
|
||||
base::SharedMemory* bitmap = new base::SharedMemory(*remote_bitmap_handle,
|
||||
false, process);
|
||||
|
||||
// We store the object where the remote handle was located so it can
|
||||
// be retrieved by the UI thread (see WriteBitmapFromSharedMemory()).
|
||||
iter->second[0].clear();
|
||||
for (size_t i = 0; i < sizeof(bitmap); i++)
|
||||
iter->second[0].push_back(reinterpret_cast<char*>(&bitmap)[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
Clipboard::FormatType Clipboard::GetWebKitSmartPasteFormatType() {
|
||||
return IntToString(ClipboardUtil::GetWebKitSmartPasteFormat()->cfFormat);
|
||||
}
|
||||
|
||||
// static
|
||||
void Clipboard::FreeData(unsigned int format, HANDLE data) {
|
||||
if (format == CF_BITMAP)
|
||||
::DeleteObject(static_cast<HBITMAP>(data));
|
||||
else
|
||||
::GlobalFree(data);
|
||||
}
|
||||
|
||||
HWND Clipboard::GetClipboardWindow() const {
|
||||
if (!clipboard_owner_ && create_window_) {
|
||||
clipboard_owner_ = ::CreateWindow(L"ClipboardOwnerWindowClass",
|
||||
L"ClipboardOwnerWindow",
|
||||
0, 0, 0, 0, 0,
|
||||
HWND_MESSAGE,
|
||||
0, 0, 0);
|
||||
}
|
||||
return clipboard_owner_;
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
#include <windows.h>
|
||||
#include <ImageHlp.h>
|
||||
#include <psapi.h>
|
||||
|
||||
#include "base/image_util.h"
|
||||
#include "base/process_util.h"
|
||||
|
||||
// imagehlp.dll appears to ship in all win versions after Win95.
|
||||
// nsylvain verified it is present in win2k.
|
||||
// Using #pragma comment for dependency, instead of LoadLibrary/GetProcAddress.
|
||||
#pragma comment(lib, "imagehlp.lib")
|
||||
|
||||
namespace image_util {
|
||||
|
||||
// ImageMetrics
|
||||
ImageMetrics::ImageMetrics(HANDLE process) : process_(process) {
|
||||
}
|
||||
|
||||
ImageMetrics::~ImageMetrics() {
|
||||
}
|
||||
|
||||
bool ImageMetrics::GetDllImageSectionData(const std::string& loaded_dll_name,
|
||||
ImageSectionsData* section_sizes) {
|
||||
// Get a handle to the loaded DLL
|
||||
HMODULE the_dll = GetModuleHandleA(loaded_dll_name.c_str());
|
||||
char full_filename[MAX_PATH];
|
||||
// Get image path
|
||||
if (GetModuleFileNameExA(process_, the_dll, full_filename, MAX_PATH)) {
|
||||
return GetImageSectionSizes(full_filename, section_sizes);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImageMetrics::GetProcessImageSectionData(ImageSectionsData*
|
||||
section_sizes) {
|
||||
char exe_path[MAX_PATH];
|
||||
// Get image path
|
||||
if (GetModuleFileNameExA(process_, NULL, exe_path, MAX_PATH)) {
|
||||
return GetImageSectionSizes(exe_path, section_sizes);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// private
|
||||
bool ImageMetrics::GetImageSectionSizes(char* qualified_path,
|
||||
ImageSectionsData* result) {
|
||||
LOADED_IMAGE li;
|
||||
// TODO (timsteele): There is no unicode version for MapAndLoad, hence
|
||||
// why ansi functions are used in this class. Should we try and rewrite
|
||||
// this call ourselves to be safe?
|
||||
if (MapAndLoad(qualified_path, 0, &li, FALSE, TRUE)) {
|
||||
IMAGE_SECTION_HEADER* section_header = li.Sections;
|
||||
for (unsigned i = 0; i < li.NumberOfSections; i++, section_header++) {
|
||||
std::string name(reinterpret_cast<char*>(section_header->Name));
|
||||
ImageSectionData data(name, section_header->Misc.VirtualSize ?
|
||||
section_header->Misc.VirtualSize :
|
||||
section_header->SizeOfRawData);
|
||||
// copy into result
|
||||
result->push_back(data);
|
||||
}
|
||||
} else {
|
||||
// map and load failed
|
||||
return false;
|
||||
}
|
||||
UnMapAndLoad(&li);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace image_util
|
@ -1,66 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This file/namespace contains utility functions for gathering
|
||||
// information about PE (Portable Executable) headers within
|
||||
// images (dll's / exe's )
|
||||
|
||||
#ifndef BASE_IMAGE_UTIL_H_
|
||||
#define BASE_IMAGE_UTIL_H_
|
||||
|
||||
#include <windows.h>
|
||||
#include <vector>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
|
||||
namespace image_util {
|
||||
|
||||
// Contains both the PE section name (.text, .reloc etc) and its size.
|
||||
struct ImageSectionData {
|
||||
ImageSectionData (const std::string& section_name, size_t section_size)
|
||||
: name (section_name),
|
||||
size_in_bytes(section_size) {
|
||||
}
|
||||
|
||||
std::string name;
|
||||
size_t size_in_bytes;
|
||||
};
|
||||
|
||||
typedef std::vector<ImageSectionData> ImageSectionsData;
|
||||
|
||||
// Provides image statistics for modules of a specified process, or for the
|
||||
// specified process' own executable file. To use, invoke CreateImageMetrics()
|
||||
// to get an instance for a specified process, then access the information via
|
||||
// methods.
|
||||
class ImageMetrics {
|
||||
public:
|
||||
// Creates an ImageMetrics instance for given process owned by
|
||||
// the caller.
|
||||
explicit ImageMetrics(HANDLE process);
|
||||
~ImageMetrics();
|
||||
|
||||
// Fills a vector of ImageSectionsData containing name/size info
|
||||
// for every section found in the specified dll's PE section table.
|
||||
// The DLL must be loaded by the process associated with this ImageMetrics
|
||||
// instance.
|
||||
bool GetDllImageSectionData(const std::string& loaded_dll_name,
|
||||
ImageSectionsData* section_sizes);
|
||||
|
||||
// Fills a vector if ImageSectionsData containing name/size info
|
||||
// for every section found in the executable file of the process
|
||||
// associated with this ImageMetrics instance.
|
||||
bool GetProcessImageSectionData(ImageSectionsData* section_sizes);
|
||||
|
||||
private:
|
||||
// Helper for GetDllImageSectionData and GetProcessImageSectionData
|
||||
bool GetImageSectionSizes(char* qualified_path, ImageSectionsData* result);
|
||||
|
||||
HANDLE process_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ImageMetrics);
|
||||
};
|
||||
|
||||
} // namespace image_util
|
||||
|
||||
#endif // BASE_IMAGE_UTIL_H_
|
@ -1,641 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/json_reader.h"
|
||||
|
||||
#include "base/float_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "base/string_util.h"
|
||||
#include "base/values.h"
|
||||
|
||||
static const JSONReader::Token kInvalidToken(JSONReader::Token::INVALID_TOKEN,
|
||||
0, 0);
|
||||
static const int kStackLimit = 100;
|
||||
|
||||
namespace {
|
||||
|
||||
inline int HexToInt(wchar_t c) {
|
||||
if ('0' <= c && c <= '9') {
|
||||
return c - '0';
|
||||
} else if ('A' <= c && c <= 'F') {
|
||||
return c - 'A' + 10;
|
||||
} else if ('a' <= c && c <= 'f') {
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
NOTREACHED();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// A helper method for ParseNumberToken. It reads an int from the end of
|
||||
// token. The method returns false if there is no valid integer at the end of
|
||||
// the token.
|
||||
bool ReadInt(JSONReader::Token& token, bool can_have_leading_zeros) {
|
||||
wchar_t first = token.NextChar();
|
||||
int len = 0;
|
||||
|
||||
// Read in more digits
|
||||
wchar_t c = first;
|
||||
while ('\0' != c && '0' <= c && c <= '9') {
|
||||
++token.length;
|
||||
++len;
|
||||
c = token.NextChar();
|
||||
}
|
||||
// We need at least 1 digit.
|
||||
if (len == 0)
|
||||
return false;
|
||||
|
||||
if (!can_have_leading_zeros && len > 1 && '0' == first)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// A helper method for ParseStringToken. It reads |digits| hex digits from the
|
||||
// token. If the sequence if digits is not valid (contains other characters),
|
||||
// the method returns false.
|
||||
bool ReadHexDigits(JSONReader::Token& token, int digits) {
|
||||
for (int i = 1; i <= digits; ++i) {
|
||||
wchar_t c = *(token.begin + token.length + i);
|
||||
if ('\0' == c)
|
||||
return false;
|
||||
if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'f') ||
|
||||
('A' <= c && c <= 'F'))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
token.length += digits;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
const char* JSONReader::kBadRootElementType =
|
||||
"Root value must be an array or object.";
|
||||
const char* JSONReader::kInvalidEscape =
|
||||
"Invalid escape sequence.";
|
||||
const char* JSONReader::kSyntaxError =
|
||||
"Syntax error.";
|
||||
const char* JSONReader::kTrailingComma =
|
||||
"Trailing comma not allowed.";
|
||||
const char* JSONReader::kTooMuchNesting =
|
||||
"Too much nesting.";
|
||||
const char* JSONReader::kUnexpectedDataAfterRoot =
|
||||
"Unexpected data after root element.";
|
||||
const char* JSONReader::kUnsupportedEncoding =
|
||||
"Unsupported encoding. JSON must be UTF-8.";
|
||||
const char* JSONReader::kUnquotedDictionaryKey =
|
||||
"Dictionary keys must be quoted.";
|
||||
|
||||
/* static */
|
||||
Value* JSONReader::Read(const std::string& json,
|
||||
bool allow_trailing_comma) {
|
||||
return ReadAndReturnError(json, allow_trailing_comma, NULL);
|
||||
}
|
||||
|
||||
/* static */
|
||||
Value* JSONReader::ReadAndReturnError(const std::string& json,
|
||||
bool allow_trailing_comma,
|
||||
std::string *error_message_out) {
|
||||
JSONReader reader = JSONReader();
|
||||
Value* root = reader.JsonToValue(json, true, allow_trailing_comma);
|
||||
if (root)
|
||||
return root;
|
||||
|
||||
if (error_message_out)
|
||||
*error_message_out = reader.error_message();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* static */
|
||||
std::string JSONReader::FormatErrorMessage(int line, int column,
|
||||
const char* description) {
|
||||
return StringPrintf("Line: %i, column: %i, %s",
|
||||
line, column, description);
|
||||
}
|
||||
|
||||
JSONReader::JSONReader()
|
||||
: start_pos_(NULL), json_pos_(NULL), stack_depth_(0),
|
||||
allow_trailing_comma_(false) {}
|
||||
|
||||
Value* JSONReader::JsonToValue(const std::string& json, bool check_root,
|
||||
bool allow_trailing_comma) {
|
||||
// The input must be in UTF-8.
|
||||
if (!IsStringUTF8(json.c_str())) {
|
||||
error_message_ = kUnsupportedEncoding;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// The conversion from UTF8 to wstring removes null bytes for us
|
||||
// (a good thing).
|
||||
std::wstring json_wide(UTF8ToWide(json));
|
||||
start_pos_ = json_wide.c_str();
|
||||
|
||||
// When the input JSON string starts with a UTF-8 Byte-Order-Mark
|
||||
// (0xEF, 0xBB, 0xBF), the UTF8ToWide() function converts it to a Unicode
|
||||
// BOM (U+FEFF). To avoid the JSONReader::BuildValue() function from
|
||||
// mis-treating a Unicode BOM as an invalid character and returning NULL,
|
||||
// skip a converted Unicode BOM if it exists.
|
||||
if (!json_wide.empty() && start_pos_[0] == 0xFEFF) {
|
||||
++start_pos_;
|
||||
}
|
||||
|
||||
json_pos_ = start_pos_;
|
||||
allow_trailing_comma_ = allow_trailing_comma;
|
||||
stack_depth_ = 0;
|
||||
error_message_.clear();
|
||||
|
||||
scoped_ptr<Value> root(BuildValue(check_root));
|
||||
if (root.get()) {
|
||||
if (ParseToken().type == Token::END_OF_INPUT) {
|
||||
return root.release();
|
||||
} else {
|
||||
SetErrorMessage(kUnexpectedDataAfterRoot, json_pos_);
|
||||
}
|
||||
}
|
||||
|
||||
// Default to calling errors "syntax errors".
|
||||
if (error_message_.empty())
|
||||
SetErrorMessage(kSyntaxError, json_pos_);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Value* JSONReader::BuildValue(bool is_root) {
|
||||
++stack_depth_;
|
||||
if (stack_depth_ > kStackLimit) {
|
||||
SetErrorMessage(kTooMuchNesting, json_pos_);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Token token = ParseToken();
|
||||
// The root token must be an array or an object.
|
||||
if (is_root && token.type != Token::OBJECT_BEGIN &&
|
||||
token.type != Token::ARRAY_BEGIN) {
|
||||
SetErrorMessage(kBadRootElementType, json_pos_);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
scoped_ptr<Value> node;
|
||||
|
||||
switch (token.type) {
|
||||
case Token::END_OF_INPUT:
|
||||
case Token::INVALID_TOKEN:
|
||||
return NULL;
|
||||
|
||||
case Token::NULL_TOKEN:
|
||||
node.reset(Value::CreateNullValue());
|
||||
break;
|
||||
|
||||
case Token::BOOL_TRUE:
|
||||
node.reset(Value::CreateBooleanValue(true));
|
||||
break;
|
||||
|
||||
case Token::BOOL_FALSE:
|
||||
node.reset(Value::CreateBooleanValue(false));
|
||||
break;
|
||||
|
||||
case Token::NUMBER:
|
||||
node.reset(DecodeNumber(token));
|
||||
if (!node.get())
|
||||
return NULL;
|
||||
break;
|
||||
|
||||
case Token::STRING:
|
||||
node.reset(DecodeString(token));
|
||||
if (!node.get())
|
||||
return NULL;
|
||||
break;
|
||||
|
||||
case Token::ARRAY_BEGIN:
|
||||
{
|
||||
json_pos_ += token.length;
|
||||
token = ParseToken();
|
||||
|
||||
node.reset(new ListValue());
|
||||
while (token.type != Token::ARRAY_END) {
|
||||
Value* array_node = BuildValue(false);
|
||||
if (!array_node)
|
||||
return NULL;
|
||||
static_cast<ListValue*>(node.get())->Append(array_node);
|
||||
|
||||
// After a list value, we expect a comma or the end of the list.
|
||||
token = ParseToken();
|
||||
if (token.type == Token::LIST_SEPARATOR) {
|
||||
json_pos_ += token.length;
|
||||
token = ParseToken();
|
||||
// Trailing commas are invalid according to the JSON RFC, but some
|
||||
// consumers need the parsing leniency, so handle accordingly.
|
||||
if (token.type == Token::ARRAY_END) {
|
||||
if (!allow_trailing_comma_) {
|
||||
SetErrorMessage(kTrailingComma, json_pos_);
|
||||
return NULL;
|
||||
}
|
||||
// Trailing comma OK, stop parsing the Array.
|
||||
break;
|
||||
}
|
||||
} else if (token.type != Token::ARRAY_END) {
|
||||
// Unexpected value after list value. Bail out.
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (token.type != Token::ARRAY_END) {
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Token::OBJECT_BEGIN:
|
||||
{
|
||||
json_pos_ += token.length;
|
||||
token = ParseToken();
|
||||
|
||||
node.reset(new DictionaryValue);
|
||||
while (token.type != Token::OBJECT_END) {
|
||||
if (token.type != Token::STRING) {
|
||||
SetErrorMessage(kUnquotedDictionaryKey, json_pos_);
|
||||
return NULL;
|
||||
}
|
||||
scoped_ptr<Value> dict_key_value(DecodeString(token));
|
||||
if (!dict_key_value.get())
|
||||
return NULL;
|
||||
|
||||
// Convert the key into a wstring.
|
||||
std::wstring dict_key;
|
||||
bool success = dict_key_value->GetAsString(&dict_key);
|
||||
DCHECK(success);
|
||||
|
||||
json_pos_ += token.length;
|
||||
token = ParseToken();
|
||||
if (token.type != Token::OBJECT_PAIR_SEPARATOR)
|
||||
return NULL;
|
||||
|
||||
json_pos_ += token.length;
|
||||
token = ParseToken();
|
||||
Value* dict_value = BuildValue(false);
|
||||
if (!dict_value)
|
||||
return NULL;
|
||||
static_cast<DictionaryValue*>(node.get())->Set(dict_key, dict_value);
|
||||
|
||||
// After a key/value pair, we expect a comma or the end of the
|
||||
// object.
|
||||
token = ParseToken();
|
||||
if (token.type == Token::LIST_SEPARATOR) {
|
||||
json_pos_ += token.length;
|
||||
token = ParseToken();
|
||||
// Trailing commas are invalid according to the JSON RFC, but some
|
||||
// consumers need the parsing leniency, so handle accordingly.
|
||||
if (token.type == Token::OBJECT_END) {
|
||||
if (!allow_trailing_comma_) {
|
||||
SetErrorMessage(kTrailingComma, json_pos_);
|
||||
return NULL;
|
||||
}
|
||||
// Trailing comma OK, stop parsing the Object.
|
||||
break;
|
||||
}
|
||||
} else if (token.type != Token::OBJECT_END) {
|
||||
// Unexpected value after last object value. Bail out.
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (token.type != Token::OBJECT_END)
|
||||
return NULL;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// We got a token that's not a value.
|
||||
return NULL;
|
||||
}
|
||||
json_pos_ += token.length;
|
||||
|
||||
--stack_depth_;
|
||||
return node.release();
|
||||
}
|
||||
|
||||
JSONReader::Token JSONReader::ParseNumberToken() {
|
||||
// We just grab the number here. We validate the size in DecodeNumber.
|
||||
// According to RFC4627, a valid number is: [minus] int [frac] [exp]
|
||||
Token token(Token::NUMBER, json_pos_, 0);
|
||||
wchar_t c = *json_pos_;
|
||||
if ('-' == c) {
|
||||
++token.length;
|
||||
c = token.NextChar();
|
||||
}
|
||||
|
||||
if (!ReadInt(token, false))
|
||||
return kInvalidToken;
|
||||
|
||||
// Optional fraction part
|
||||
c = token.NextChar();
|
||||
if ('.' == c) {
|
||||
++token.length;
|
||||
if (!ReadInt(token, true))
|
||||
return kInvalidToken;
|
||||
c = token.NextChar();
|
||||
}
|
||||
|
||||
// Optional exponent part
|
||||
if ('e' == c || 'E' == c) {
|
||||
++token.length;
|
||||
c = token.NextChar();
|
||||
if ('-' == c || '+' == c) {
|
||||
++token.length;
|
||||
c = token.NextChar();
|
||||
}
|
||||
if (!ReadInt(token, true))
|
||||
return kInvalidToken;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
Value* JSONReader::DecodeNumber(const Token& token) {
|
||||
const std::wstring num_string(token.begin, token.length);
|
||||
|
||||
int num_int;
|
||||
if (StringToInt(WideToUTF16Hack(num_string), &num_int))
|
||||
return Value::CreateIntegerValue(num_int);
|
||||
|
||||
double num_double;
|
||||
if (StringToDouble(WideToUTF16Hack(num_string), &num_double) &&
|
||||
base::IsFinite(num_double))
|
||||
return Value::CreateRealValue(num_double);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JSONReader::Token JSONReader::ParseStringToken() {
|
||||
Token token(Token::STRING, json_pos_, 1);
|
||||
wchar_t c = token.NextChar();
|
||||
while ('\0' != c) {
|
||||
if ('\\' == c) {
|
||||
++token.length;
|
||||
c = token.NextChar();
|
||||
// Make sure the escaped char is valid.
|
||||
switch (c) {
|
||||
case 'x':
|
||||
if (!ReadHexDigits(token, 2)) {
|
||||
SetErrorMessage(kInvalidEscape, json_pos_ + token.length);
|
||||
return kInvalidToken;
|
||||
}
|
||||
break;
|
||||
case 'u':
|
||||
if (!ReadHexDigits(token, 4)) {
|
||||
SetErrorMessage(kInvalidEscape, json_pos_ + token.length);
|
||||
return kInvalidToken;
|
||||
}
|
||||
break;
|
||||
case '\\':
|
||||
case '/':
|
||||
case 'b':
|
||||
case 'f':
|
||||
case 'n':
|
||||
case 'r':
|
||||
case 't':
|
||||
case 'v':
|
||||
case '"':
|
||||
break;
|
||||
default:
|
||||
SetErrorMessage(kInvalidEscape, json_pos_ + token.length);
|
||||
return kInvalidToken;
|
||||
}
|
||||
} else if ('"' == c) {
|
||||
++token.length;
|
||||
return token;
|
||||
}
|
||||
++token.length;
|
||||
c = token.NextChar();
|
||||
}
|
||||
return kInvalidToken;
|
||||
}
|
||||
|
||||
Value* JSONReader::DecodeString(const Token& token) {
|
||||
std::wstring decoded_str;
|
||||
decoded_str.reserve(token.length - 2);
|
||||
|
||||
for (int i = 1; i < token.length - 1; ++i) {
|
||||
wchar_t c = *(token.begin + i);
|
||||
if ('\\' == c) {
|
||||
++i;
|
||||
c = *(token.begin + i);
|
||||
switch (c) {
|
||||
case '"':
|
||||
case '/':
|
||||
case '\\':
|
||||
decoded_str.push_back(c);
|
||||
break;
|
||||
case 'b':
|
||||
decoded_str.push_back('\b');
|
||||
break;
|
||||
case 'f':
|
||||
decoded_str.push_back('\f');
|
||||
break;
|
||||
case 'n':
|
||||
decoded_str.push_back('\n');
|
||||
break;
|
||||
case 'r':
|
||||
decoded_str.push_back('\r');
|
||||
break;
|
||||
case 't':
|
||||
decoded_str.push_back('\t');
|
||||
break;
|
||||
case 'v':
|
||||
decoded_str.push_back('\v');
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
decoded_str.push_back((HexToInt(*(token.begin + i + 1)) << 4) +
|
||||
HexToInt(*(token.begin + i + 2)));
|
||||
i += 2;
|
||||
break;
|
||||
case 'u':
|
||||
decoded_str.push_back((HexToInt(*(token.begin + i + 1)) << 12 ) +
|
||||
(HexToInt(*(token.begin + i + 2)) << 8) +
|
||||
(HexToInt(*(token.begin + i + 3)) << 4) +
|
||||
HexToInt(*(token.begin + i + 4)));
|
||||
i += 4;
|
||||
break;
|
||||
|
||||
default:
|
||||
// We should only have valid strings at this point. If not,
|
||||
// ParseStringToken didn't do it's job.
|
||||
NOTREACHED();
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
// Not escaped
|
||||
decoded_str.push_back(c);
|
||||
}
|
||||
}
|
||||
return Value::CreateStringValue(decoded_str);
|
||||
}
|
||||
|
||||
JSONReader::Token JSONReader::ParseToken() {
|
||||
static const std::wstring kNullString(L"null");
|
||||
static const std::wstring kTrueString(L"true");
|
||||
static const std::wstring kFalseString(L"false");
|
||||
|
||||
EatWhitespaceAndComments();
|
||||
|
||||
Token token(Token::INVALID_TOKEN, 0, 0);
|
||||
switch (*json_pos_) {
|
||||
case '\0':
|
||||
token.type = Token::END_OF_INPUT;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
if (NextStringMatch(kNullString))
|
||||
token = Token(Token::NULL_TOKEN, json_pos_, 4);
|
||||
break;
|
||||
|
||||
case 't':
|
||||
if (NextStringMatch(kTrueString))
|
||||
token = Token(Token::BOOL_TRUE, json_pos_, 4);
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
if (NextStringMatch(kFalseString))
|
||||
token = Token(Token::BOOL_FALSE, json_pos_, 5);
|
||||
break;
|
||||
|
||||
case '[':
|
||||
token = Token(Token::ARRAY_BEGIN, json_pos_, 1);
|
||||
break;
|
||||
|
||||
case ']':
|
||||
token = Token(Token::ARRAY_END, json_pos_, 1);
|
||||
break;
|
||||
|
||||
case ',':
|
||||
token = Token(Token::LIST_SEPARATOR, json_pos_, 1);
|
||||
break;
|
||||
|
||||
case '{':
|
||||
token = Token(Token::OBJECT_BEGIN, json_pos_, 1);
|
||||
break;
|
||||
|
||||
case '}':
|
||||
token = Token(Token::OBJECT_END, json_pos_, 1);
|
||||
break;
|
||||
|
||||
case ':':
|
||||
token = Token(Token::OBJECT_PAIR_SEPARATOR, json_pos_, 1);
|
||||
break;
|
||||
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
case '-':
|
||||
token = ParseNumberToken();
|
||||
break;
|
||||
|
||||
case '"':
|
||||
token = ParseStringToken();
|
||||
break;
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
bool JSONReader::NextStringMatch(const std::wstring& str) {
|
||||
for (size_t i = 0; i < str.length(); ++i) {
|
||||
if ('\0' == *json_pos_)
|
||||
return false;
|
||||
if (*(json_pos_ + i) != str[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void JSONReader::EatWhitespaceAndComments() {
|
||||
while ('\0' != *json_pos_) {
|
||||
switch (*json_pos_) {
|
||||
case ' ':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
++json_pos_;
|
||||
break;
|
||||
case '/':
|
||||
// TODO(tc): This isn't in the RFC so it should be a parser flag.
|
||||
if (!EatComment())
|
||||
return;
|
||||
break;
|
||||
default:
|
||||
// Not a whitespace char, just exit.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool JSONReader::EatComment() {
|
||||
if ('/' != *json_pos_)
|
||||
return false;
|
||||
|
||||
wchar_t next_char = *(json_pos_ + 1);
|
||||
if ('/' == next_char) {
|
||||
// Line comment, read until \n or \r
|
||||
json_pos_ += 2;
|
||||
while ('\0' != *json_pos_) {
|
||||
switch (*json_pos_) {
|
||||
case '\n':
|
||||
case '\r':
|
||||
++json_pos_;
|
||||
return true;
|
||||
default:
|
||||
++json_pos_;
|
||||
}
|
||||
}
|
||||
} else if ('*' == next_char) {
|
||||
// Block comment, read until */
|
||||
json_pos_ += 2;
|
||||
while ('\0' != *json_pos_) {
|
||||
switch (*json_pos_) {
|
||||
case '*':
|
||||
if ('/' == *(json_pos_ + 1)) {
|
||||
json_pos_ += 2;
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
++json_pos_;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void JSONReader::SetErrorMessage(const char* description,
|
||||
const wchar_t* error_pos) {
|
||||
int line_number = 1;
|
||||
int column_number = 1;
|
||||
|
||||
// Figure out the line and column the error occured at.
|
||||
for (const wchar_t* pos = start_pos_; pos != error_pos; ++pos) {
|
||||
if (*pos == '\0') {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
|
||||
if (*pos == '\n') {
|
||||
++line_number;
|
||||
column_number = 1;
|
||||
} else {
|
||||
++column_number;
|
||||
}
|
||||
}
|
||||
|
||||
error_message_ = FormatErrorMessage(line_number, column_number, description);
|
||||
}
|
@ -1,186 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
//
|
||||
// A JSON parser. Converts strings of JSON into a Value object (see
|
||||
// base/values.h).
|
||||
// http://www.ietf.org/rfc/rfc4627.txt?number=4627
|
||||
//
|
||||
// Known limitations/deviations from the RFC:
|
||||
// - Only knows how to parse ints within the range of a signed 32 bit int and
|
||||
// decimal numbers within a double.
|
||||
// - Assumes input is encoded as UTF8. The spec says we should allow UTF-16
|
||||
// (BE or LE) and UTF-32 (BE or LE) as well.
|
||||
// - We limit nesting to 100 levels to prevent stack overflow (this is allowed
|
||||
// by the RFC).
|
||||
// - A Unicode FAQ ("http://unicode.org/faq/utf_bom.html") writes a data
|
||||
// stream may start with a Unicode Byte-Order-Mark (U+FEFF), i.e. the input
|
||||
// UTF-8 string for the JSONReader::JsonToValue() function may start with a
|
||||
// UTF-8 BOM (0xEF, 0xBB, 0xBF).
|
||||
// To avoid the function from mis-treating a UTF-8 BOM as an invalid
|
||||
// character, the function skips a Unicode BOM at the beginning of the
|
||||
// Unicode string (converted from the input UTF-8 string) before parsing it.
|
||||
//
|
||||
// TODO(tc): Add a parsing option to to relax object keys being wrapped in
|
||||
// double quotes
|
||||
// TODO(tc): Add an option to disable comment stripping
|
||||
// TODO(aa): Consider making the constructor public and the static Read() method
|
||||
// only a convenience for the common uses with more complex configuration going
|
||||
// on the instance.
|
||||
|
||||
#ifndef BASE_JSON_READER_H_
|
||||
#define BASE_JSON_READER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "testing/gtest/include/gtest/gtest_prod.h"
|
||||
|
||||
class Value;
|
||||
|
||||
class JSONReader {
|
||||
public:
|
||||
// A struct to hold a JS token.
|
||||
class Token {
|
||||
public:
|
||||
enum Type {
|
||||
OBJECT_BEGIN, // {
|
||||
OBJECT_END, // }
|
||||
ARRAY_BEGIN, // [
|
||||
ARRAY_END, // ]
|
||||
STRING,
|
||||
NUMBER,
|
||||
BOOL_TRUE, // true
|
||||
BOOL_FALSE, // false
|
||||
NULL_TOKEN, // null
|
||||
LIST_SEPARATOR, // ,
|
||||
OBJECT_PAIR_SEPARATOR, // :
|
||||
END_OF_INPUT,
|
||||
INVALID_TOKEN,
|
||||
};
|
||||
Token(Type t, const wchar_t* b, int len)
|
||||
: type(t), begin(b), length(len) {}
|
||||
|
||||
Type type;
|
||||
|
||||
// A pointer into JSONReader::json_pos_ that's the beginning of this token.
|
||||
const wchar_t* begin;
|
||||
|
||||
// End should be one char past the end of the token.
|
||||
int length;
|
||||
|
||||
// Get the character that's one past the end of this token.
|
||||
wchar_t NextChar() {
|
||||
return *(begin + length);
|
||||
}
|
||||
};
|
||||
|
||||
// Error messages that can be returned.
|
||||
static const char* kBadRootElementType;
|
||||
static const char* kInvalidEscape;
|
||||
static const char* kSyntaxError;
|
||||
static const char* kTrailingComma;
|
||||
static const char* kTooMuchNesting;
|
||||
static const char* kUnexpectedDataAfterRoot;
|
||||
static const char* kUnsupportedEncoding;
|
||||
static const char* kUnquotedDictionaryKey;
|
||||
|
||||
JSONReader();
|
||||
|
||||
// Reads and parses |json|, returning a Value. The caller owns the returned
|
||||
// instance. If |json| is not a properly formed JSON string, returns NULL.
|
||||
// If |allow_trailing_comma| is true, we will ignore trailing commas in
|
||||
// objects and arrays even though this goes against the RFC.
|
||||
static Value* Read(const std::string& json, bool allow_trailing_comma);
|
||||
|
||||
// Reads and parses |json| like Read(). |error_message_out| is optional. If
|
||||
// specified and NULL is returned, |error_message_out| will be populated with
|
||||
// a string describing the error. Otherwise, |error_message_out| is
|
||||
// unmodified.
|
||||
static Value* ReadAndReturnError(const std::string& json,
|
||||
bool allow_trailing_comma,
|
||||
std::string* error_message_out);
|
||||
|
||||
// Returns the error message if the last call to JsonToValue() failed. If the
|
||||
// last call did not fail, returns a valid empty string.
|
||||
std::string error_message() { return error_message_; }
|
||||
|
||||
// Reads and parses |json|, returning a Value. The caller owns the returned
|
||||
// instance. If |json| is not a properly formed JSON string, returns NULL and
|
||||
// a detailed error can be retrieved from |error_message()|.
|
||||
// If |check_root| is true, we require that the root object be an object or
|
||||
// array. Otherwise, it can be any valid JSON type.
|
||||
// If |allow_trailing_comma| is true, we will ignore trailing commas in
|
||||
// objects and arrays even though this goes against the RFC.
|
||||
Value* JsonToValue(const std::string& json, bool check_root,
|
||||
bool allow_trailing_comma);
|
||||
|
||||
private:
|
||||
static std::string FormatErrorMessage(int line, int column,
|
||||
const char* description);
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(JSONReader);
|
||||
|
||||
FRIEND_TEST(JSONReaderTest, Reading);
|
||||
FRIEND_TEST(JSONReaderTest, ErrorMessages);
|
||||
|
||||
// Recursively build Value. Returns NULL if we don't have a valid JSON
|
||||
// string. If |is_root| is true, we verify that the root element is either
|
||||
// an object or an array.
|
||||
Value* BuildValue(bool is_root);
|
||||
|
||||
// Parses a sequence of characters into a Token::NUMBER. If the sequence of
|
||||
// characters is not a valid number, returns a Token::INVALID_TOKEN. Note
|
||||
// that DecodeNumber is used to actually convert from a string to an
|
||||
// int/double.
|
||||
Token ParseNumberToken();
|
||||
|
||||
// Try and convert the substring that token holds into an int or a double. If
|
||||
// we can (ie., no overflow), return the value, else return NULL.
|
||||
Value* DecodeNumber(const Token& token);
|
||||
|
||||
// Parses a sequence of characters into a Token::STRING. If the sequence of
|
||||
// characters is not a valid string, returns a Token::INVALID_TOKEN. Note
|
||||
// that DecodeString is used to actually decode the escaped string into an
|
||||
// actual wstring.
|
||||
Token ParseStringToken();
|
||||
|
||||
// Convert the substring into a value string. This should always succeed
|
||||
// (otherwise ParseStringToken would have failed).
|
||||
Value* DecodeString(const Token& token);
|
||||
|
||||
// Grabs the next token in the JSON stream. This does not increment the
|
||||
// stream so it can be used to look ahead at the next token.
|
||||
Token ParseToken();
|
||||
|
||||
// Increments |json_pos_| past leading whitespace and comments.
|
||||
void EatWhitespaceAndComments();
|
||||
|
||||
// If |json_pos_| is at the start of a comment, eat it, otherwise, returns
|
||||
// false.
|
||||
bool EatComment();
|
||||
|
||||
// Checks if |json_pos_| matches str.
|
||||
bool NextStringMatch(const std::wstring& str);
|
||||
|
||||
// Creates the error message that will be returned to the caller. The current
|
||||
// line and column are determined and added into the final message.
|
||||
void SetErrorMessage(const char* description, const wchar_t* error_pos);
|
||||
|
||||
// Pointer to the starting position in the input string.
|
||||
const wchar_t* start_pos_;
|
||||
|
||||
// Pointer to the current position in the input string.
|
||||
const wchar_t* json_pos_;
|
||||
|
||||
// Used to keep track of how many nested lists/dicts there are.
|
||||
int stack_depth_;
|
||||
|
||||
// A parser flag that allows trailing commas in objects and arrays.
|
||||
bool allow_trailing_comma_;
|
||||
|
||||
// Contains the error message for the last call to JsonToValue(), if any.
|
||||
std::string error_message_;
|
||||
};
|
||||
|
||||
#endif // BASE_JSON_READER_H_
|
@ -1,492 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "base/json_reader.h"
|
||||
#include "base/scoped_ptr.h"
|
||||
#include "base/values.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
TEST(JSONReaderTest, Reading) {
|
||||
// some whitespace checking
|
||||
scoped_ptr<Value> root;
|
||||
root.reset(JSONReader().JsonToValue(" null ", false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_NULL));
|
||||
|
||||
// Invalid JSON string
|
||||
root.reset(JSONReader().JsonToValue("nu", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Simple bool
|
||||
root.reset(JSONReader().JsonToValue("true ", false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_BOOLEAN));
|
||||
|
||||
// Test number formats
|
||||
root.reset(JSONReader().JsonToValue("43", false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_INTEGER));
|
||||
int int_val = 0;
|
||||
ASSERT_TRUE(root->GetAsInteger(&int_val));
|
||||
ASSERT_EQ(43, int_val);
|
||||
|
||||
// According to RFC4627, oct, hex, and leading zeros are invalid JSON.
|
||||
root.reset(JSONReader().JsonToValue("043", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("0x43", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("00", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Test 0 (which needs to be special cased because of the leading zero
|
||||
// clause).
|
||||
root.reset(JSONReader().JsonToValue("0", false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_INTEGER));
|
||||
int_val = 1;
|
||||
ASSERT_TRUE(root->GetAsInteger(&int_val));
|
||||
ASSERT_EQ(0, int_val);
|
||||
|
||||
// Numbers that overflow ints should succeed, being internally promoted to
|
||||
// storage as doubles
|
||||
root.reset(JSONReader().JsonToValue("2147483648", false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
double real_val;
|
||||
#ifdef ARCH_CPU_32_BITS
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_REAL));
|
||||
real_val = 0.0;
|
||||
ASSERT_TRUE(root->GetAsReal(&real_val));
|
||||
ASSERT_DOUBLE_EQ(2147483648.0, real_val);
|
||||
#else
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_INTEGER));
|
||||
int_val = 0;
|
||||
ASSERT_TRUE(root->GetAsInteger(&int_val));
|
||||
ASSERT_EQ(2147483648, int_val);
|
||||
#endif
|
||||
root.reset(JSONReader().JsonToValue("-2147483649", false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
#ifdef ARCH_CPU_32_BITS
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_REAL));
|
||||
real_val = 0.0;
|
||||
ASSERT_TRUE(root->GetAsReal(&real_val));
|
||||
ASSERT_DOUBLE_EQ(-2147483649.0, real_val);
|
||||
#else
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_INTEGER));
|
||||
int_val = 0;
|
||||
ASSERT_TRUE(root->GetAsInteger(&int_val));
|
||||
ASSERT_EQ(-2147483649, int_val);
|
||||
#endif
|
||||
|
||||
// Parse a double
|
||||
root.reset(JSONReader().JsonToValue("43.1", false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_REAL));
|
||||
real_val = 0.0;
|
||||
ASSERT_TRUE(root->GetAsReal(&real_val));
|
||||
ASSERT_DOUBLE_EQ(43.1, real_val);
|
||||
|
||||
root.reset(JSONReader().JsonToValue("4.3e-1", false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_REAL));
|
||||
real_val = 0.0;
|
||||
ASSERT_TRUE(root->GetAsReal(&real_val));
|
||||
ASSERT_DOUBLE_EQ(.43, real_val);
|
||||
|
||||
root.reset(JSONReader().JsonToValue("2.1e0", false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_REAL));
|
||||
real_val = 0.0;
|
||||
ASSERT_TRUE(root->GetAsReal(&real_val));
|
||||
ASSERT_DOUBLE_EQ(2.1, real_val);
|
||||
|
||||
root.reset(JSONReader().JsonToValue("2.1e+0001", false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_REAL));
|
||||
real_val = 0.0;
|
||||
ASSERT_TRUE(root->GetAsReal(&real_val));
|
||||
ASSERT_DOUBLE_EQ(21.0, real_val);
|
||||
|
||||
root.reset(JSONReader().JsonToValue("0.01", false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_REAL));
|
||||
real_val = 0.0;
|
||||
ASSERT_TRUE(root->GetAsReal(&real_val));
|
||||
ASSERT_DOUBLE_EQ(0.01, real_val);
|
||||
|
||||
root.reset(JSONReader().JsonToValue("1.00", false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_REAL));
|
||||
real_val = 0.0;
|
||||
ASSERT_TRUE(root->GetAsReal(&real_val));
|
||||
ASSERT_DOUBLE_EQ(1.0, real_val);
|
||||
|
||||
// Fractional parts must have a digit before and after the decimal point.
|
||||
root.reset(JSONReader().JsonToValue("1.", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue(".1", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("1.e10", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Exponent must have a digit following the 'e'.
|
||||
root.reset(JSONReader().JsonToValue("1e", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("1E", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("1e1.", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("1e1.0", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// INF/-INF/NaN are not valid
|
||||
root.reset(JSONReader().JsonToValue("1e1000", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("-1e1000", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("NaN", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("nan", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("inf", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Invalid number formats
|
||||
root.reset(JSONReader().JsonToValue("4.3.1", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("4e3.1", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Test string parser
|
||||
root.reset(JSONReader().JsonToValue("\"hello world\"", false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_STRING));
|
||||
std::wstring str_val;
|
||||
ASSERT_TRUE(root->GetAsString(&str_val));
|
||||
ASSERT_EQ(L"hello world", str_val);
|
||||
|
||||
// Empty string
|
||||
root.reset(JSONReader().JsonToValue("\"\"", false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_STRING));
|
||||
str_val.clear();
|
||||
ASSERT_TRUE(root->GetAsString(&str_val));
|
||||
ASSERT_EQ(L"", str_val);
|
||||
|
||||
// Test basic string escapes
|
||||
root.reset(JSONReader().JsonToValue("\" \\\"\\\\\\/\\b\\f\\n\\r\\t\\v\"",
|
||||
false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_STRING));
|
||||
str_val.clear();
|
||||
ASSERT_TRUE(root->GetAsString(&str_val));
|
||||
ASSERT_EQ(L" \"\\/\b\f\n\r\t\v", str_val);
|
||||
|
||||
// Test hex and unicode escapes including the null character.
|
||||
root.reset(JSONReader().JsonToValue("\"\\x41\\x00\\u1234\"", false,
|
||||
false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_STRING));
|
||||
str_val.clear();
|
||||
ASSERT_TRUE(root->GetAsString(&str_val));
|
||||
ASSERT_EQ(std::wstring(L"A\0\x1234", 3), str_val);
|
||||
|
||||
// Test invalid strings
|
||||
root.reset(JSONReader().JsonToValue("\"no closing quote", false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("\"\\z invalid escape char\"", false,
|
||||
false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("\"\\xAQ invalid hex code\"", false,
|
||||
false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("not enough hex chars\\x1\"", false,
|
||||
false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("\"not enough escape chars\\u123\"",
|
||||
false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("\"extra backslash at end of input\\\"",
|
||||
false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Basic array
|
||||
root.reset(JSONReader::Read("[true, false, null]", false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_LIST));
|
||||
ListValue* list = static_cast<ListValue*>(root.get());
|
||||
ASSERT_EQ(3U, list->GetSize());
|
||||
|
||||
// Test with trailing comma. Should be parsed the same as above.
|
||||
scoped_ptr<Value> root2;
|
||||
root2.reset(JSONReader::Read("[true, false, null, ]", true));
|
||||
EXPECT_TRUE(root->Equals(root2.get()));
|
||||
|
||||
// Empty array
|
||||
root.reset(JSONReader::Read("[]", false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_LIST));
|
||||
list = static_cast<ListValue*>(root.get());
|
||||
ASSERT_EQ(0U, list->GetSize());
|
||||
|
||||
// Nested arrays
|
||||
root.reset(JSONReader::Read("[[true], [], [false, [], [null]], null]",
|
||||
false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_LIST));
|
||||
list = static_cast<ListValue*>(root.get());
|
||||
ASSERT_EQ(4U, list->GetSize());
|
||||
|
||||
// Lots of trailing commas.
|
||||
root2.reset(JSONReader::Read("[[true], [], [false, [], [null, ] , ], null,]",
|
||||
true));
|
||||
EXPECT_TRUE(root->Equals(root2.get()));
|
||||
|
||||
// Invalid, missing close brace.
|
||||
root.reset(JSONReader::Read("[[true], [], [false, [], [null]], null", false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Invalid, too many commas
|
||||
root.reset(JSONReader::Read("[true,, null]", false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader::Read("[true,, null]", true));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Invalid, no commas
|
||||
root.reset(JSONReader::Read("[true null]", false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Invalid, trailing comma
|
||||
root.reset(JSONReader::Read("[true,]", false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Valid if we set |allow_trailing_comma| to true.
|
||||
root.reset(JSONReader::Read("[true,]", true));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_LIST));
|
||||
list = static_cast<ListValue*>(root.get());
|
||||
EXPECT_EQ(1U, list->GetSize());
|
||||
Value* tmp_value = NULL;
|
||||
ASSERT_TRUE(list->Get(0, &tmp_value));
|
||||
EXPECT_TRUE(tmp_value->IsType(Value::TYPE_BOOLEAN));
|
||||
bool bool_value = false;
|
||||
ASSERT_TRUE(tmp_value->GetAsBoolean(&bool_value));
|
||||
EXPECT_TRUE(bool_value);
|
||||
|
||||
// Don't allow empty elements, even if |allow_trailing_comma| is
|
||||
// true.
|
||||
root.reset(JSONReader::Read("[,]", true));
|
||||
EXPECT_FALSE(root.get());
|
||||
root.reset(JSONReader::Read("[true,,]", true));
|
||||
EXPECT_FALSE(root.get());
|
||||
root.reset(JSONReader::Read("[,true,]", true));
|
||||
EXPECT_FALSE(root.get());
|
||||
root.reset(JSONReader::Read("[true,,false]", true));
|
||||
EXPECT_FALSE(root.get());
|
||||
|
||||
// Test objects
|
||||
root.reset(JSONReader::Read("{}", false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_DICTIONARY));
|
||||
|
||||
root.reset(JSONReader::Read(
|
||||
"{\"number\":9.87654321, \"null\":null , \"\\x53\" : \"str\" }",
|
||||
false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_DICTIONARY));
|
||||
DictionaryValue* dict_val = static_cast<DictionaryValue*>(root.get());
|
||||
real_val = 0.0;
|
||||
ASSERT_TRUE(dict_val->GetReal(L"number", &real_val));
|
||||
ASSERT_DOUBLE_EQ(9.87654321, real_val);
|
||||
Value* null_val = NULL;
|
||||
ASSERT_TRUE(dict_val->Get(L"null", &null_val));
|
||||
ASSERT_TRUE(null_val->IsType(Value::TYPE_NULL));
|
||||
str_val.clear();
|
||||
ASSERT_TRUE(dict_val->GetString(L"S", &str_val));
|
||||
ASSERT_EQ(L"str", str_val);
|
||||
|
||||
root2.reset(JSONReader::Read(
|
||||
"{\"number\":9.87654321, \"null\":null , \"\\x53\" : \"str\", }", true));
|
||||
EXPECT_TRUE(root->Equals(root2.get()));
|
||||
|
||||
// Test nesting
|
||||
root.reset(JSONReader::Read(
|
||||
"{\"inner\":{\"array\":[true]},\"false\":false,\"d\":{}}", false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_DICTIONARY));
|
||||
dict_val = static_cast<DictionaryValue*>(root.get());
|
||||
DictionaryValue* inner_dict = NULL;
|
||||
ASSERT_TRUE(dict_val->GetDictionary(L"inner", &inner_dict));
|
||||
ListValue* inner_array = NULL;
|
||||
ASSERT_TRUE(inner_dict->GetList(L"array", &inner_array));
|
||||
ASSERT_EQ(1U, inner_array->GetSize());
|
||||
bool_value = true;
|
||||
ASSERT_TRUE(dict_val->GetBoolean(L"false", &bool_value));
|
||||
ASSERT_FALSE(bool_value);
|
||||
inner_dict = NULL;
|
||||
ASSERT_TRUE(dict_val->GetDictionary(L"d", &inner_dict));
|
||||
|
||||
root2.reset(JSONReader::Read(
|
||||
"{\"inner\": {\"array\":[true] , },\"false\":false,\"d\":{},}", true));
|
||||
EXPECT_TRUE(root->Equals(root2.get()));
|
||||
|
||||
// Invalid, no closing brace
|
||||
root.reset(JSONReader::Read("{\"a\": true", false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Invalid, keys must be quoted
|
||||
root.reset(JSONReader::Read("{foo:true}", false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Invalid, trailing comma
|
||||
root.reset(JSONReader::Read("{\"a\":true,}", false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Invalid, too many commas
|
||||
root.reset(JSONReader::Read("{\"a\":true,,\"b\":false}", false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader::Read("{\"a\":true,,\"b\":false}", true));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Invalid, no separator
|
||||
root.reset(JSONReader::Read("{\"a\" \"b\"}", false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Invalid, lone comma.
|
||||
root.reset(JSONReader::Read("{,}", false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader::Read("{,}", true));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader::Read("{\"a\":true,,}", true));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader::Read("{,\"a\":true}", true));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader::Read("{\"a\":true,,\"b\":false}", true));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Test stack overflow
|
||||
std::string evil(1000000, '[');
|
||||
evil.append(std::string(1000000, ']'));
|
||||
root.reset(JSONReader::Read(evil, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// A few thousand adjacent lists is fine.
|
||||
std::string not_evil("[");
|
||||
not_evil.reserve(15010);
|
||||
for (int i = 0; i < 5000; ++i) {
|
||||
not_evil.append("[],");
|
||||
}
|
||||
not_evil.append("[]]");
|
||||
root.reset(JSONReader::Read(not_evil, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_LIST));
|
||||
list = static_cast<ListValue*>(root.get());
|
||||
ASSERT_EQ(5001U, list->GetSize());
|
||||
|
||||
// Test utf8 encoded input
|
||||
root.reset(JSONReader().JsonToValue("\"\xe7\xbd\x91\xe9\xa1\xb5\"",
|
||||
false, false));
|
||||
ASSERT_TRUE(root.get());
|
||||
ASSERT_TRUE(root->IsType(Value::TYPE_STRING));
|
||||
str_val.clear();
|
||||
ASSERT_TRUE(root->GetAsString(&str_val));
|
||||
ASSERT_EQ(L"\x7f51\x9875", str_val);
|
||||
|
||||
// Test invalid utf8 encoded input
|
||||
root.reset(JSONReader().JsonToValue("\"345\xb0\xa1\xb0\xa2\"",
|
||||
false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader().JsonToValue("\"123\xc0\x81\"",
|
||||
false, false));
|
||||
ASSERT_FALSE(root.get());
|
||||
|
||||
// Test invalid root objects.
|
||||
root.reset(JSONReader::Read("null", false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader::Read("true", false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader::Read("10", false));
|
||||
ASSERT_FALSE(root.get());
|
||||
root.reset(JSONReader::Read("\"root\"", false));
|
||||
ASSERT_FALSE(root.get());
|
||||
}
|
||||
|
||||
TEST(JSONReaderTest, ErrorMessages) {
|
||||
// Error strings should not be modified in case of success.
|
||||
std::string error_message;
|
||||
scoped_ptr<Value> root;
|
||||
root.reset(JSONReader::ReadAndReturnError("[42]", false, &error_message));
|
||||
EXPECT_TRUE(error_message.empty());
|
||||
|
||||
// Test line and column counting
|
||||
const char* big_json = "[\n0,\n1,\n2,\n3,4,5,6 7,\n8,\n9\n]";
|
||||
// error here --------------------------------^
|
||||
root.reset(JSONReader::ReadAndReturnError(big_json, false, &error_message));
|
||||
EXPECT_FALSE(root.get());
|
||||
EXPECT_EQ(JSONReader::FormatErrorMessage(5, 9, JSONReader::kSyntaxError),
|
||||
error_message);
|
||||
|
||||
// Test each of the error conditions
|
||||
root.reset(JSONReader::ReadAndReturnError("{},{}", false, &error_message));
|
||||
EXPECT_FALSE(root.get());
|
||||
EXPECT_EQ(JSONReader::FormatErrorMessage(1, 3,
|
||||
JSONReader::kUnexpectedDataAfterRoot), error_message);
|
||||
|
||||
std::string nested_json;
|
||||
for (int i = 0; i < 101; ++i) {
|
||||
nested_json.insert(nested_json.begin(), '[');
|
||||
nested_json.append(1, ']');
|
||||
}
|
||||
root.reset(JSONReader::ReadAndReturnError(nested_json, false,
|
||||
&error_message));
|
||||
EXPECT_FALSE(root.get());
|
||||
EXPECT_EQ(JSONReader::FormatErrorMessage(1, 101, JSONReader::kTooMuchNesting),
|
||||
error_message);
|
||||
|
||||
root.reset(JSONReader::ReadAndReturnError("42", false, &error_message));
|
||||
EXPECT_FALSE(root.get());
|
||||
EXPECT_EQ(JSONReader::FormatErrorMessage(1, 1,
|
||||
JSONReader::kBadRootElementType), error_message);
|
||||
|
||||
root.reset(JSONReader::ReadAndReturnError("[1,]", false, &error_message));
|
||||
EXPECT_FALSE(root.get());
|
||||
EXPECT_EQ(JSONReader::FormatErrorMessage(1, 4, JSONReader::kTrailingComma),
|
||||
error_message);
|
||||
|
||||
root.reset(JSONReader::ReadAndReturnError("{foo:\"bar\"}", false,
|
||||
&error_message));
|
||||
EXPECT_FALSE(root.get());
|
||||
EXPECT_EQ(JSONReader::FormatErrorMessage(1, 2,
|
||||
JSONReader::kUnquotedDictionaryKey), error_message);
|
||||
|
||||
root.reset(JSONReader::ReadAndReturnError("{\"foo\":\"bar\",}", false,
|
||||
&error_message));
|
||||
EXPECT_FALSE(root.get());
|
||||
EXPECT_EQ(JSONReader::FormatErrorMessage(1, 14, JSONReader::kTrailingComma),
|
||||
error_message);
|
||||
|
||||
root.reset(JSONReader::ReadAndReturnError("[nu]", false, &error_message));
|
||||
EXPECT_FALSE(root.get());
|
||||
EXPECT_EQ(JSONReader::FormatErrorMessage(1, 2, JSONReader::kSyntaxError),
|
||||
error_message);
|
||||
|
||||
root.reset(JSONReader::ReadAndReturnError("[\"xxx\\xq\"]", false,
|
||||
&error_message));
|
||||
EXPECT_FALSE(root.get());
|
||||
EXPECT_EQ(JSONReader::FormatErrorMessage(1, 7, JSONReader::kInvalidEscape),
|
||||
error_message);
|
||||
|
||||
root.reset(JSONReader::ReadAndReturnError("[\"xxx\\uq\"]", false,
|
||||
&error_message));
|
||||
EXPECT_FALSE(root.get());
|
||||
EXPECT_EQ(JSONReader::FormatErrorMessage(1, 7, JSONReader::kInvalidEscape),
|
||||
error_message);
|
||||
|
||||
root.reset(JSONReader::ReadAndReturnError("[\"xxx\\q\"]", false,
|
||||
&error_message));
|
||||
EXPECT_FALSE(root.get());
|
||||
EXPECT_EQ(JSONReader::FormatErrorMessage(1, 7, JSONReader::kInvalidEscape),
|
||||
error_message);
|
||||
|
||||
}
|
@ -1,175 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/json_writer.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/string_util.h"
|
||||
#include "base/values.h"
|
||||
#include "base/string_escape.h"
|
||||
|
||||
const char kPrettyPrintLineEnding[] = "\r\n";
|
||||
|
||||
/* static */
|
||||
void JSONWriter::Write(const Value* const node, bool pretty_print,
|
||||
std::string* json) {
|
||||
json->clear();
|
||||
// Is there a better way to estimate the size of the output?
|
||||
json->reserve(1024);
|
||||
JSONWriter writer(pretty_print, json);
|
||||
writer.BuildJSONString(node, 0);
|
||||
if (pretty_print)
|
||||
json->append(kPrettyPrintLineEnding);
|
||||
}
|
||||
|
||||
JSONWriter::JSONWriter(bool pretty_print, std::string* json)
|
||||
: json_string_(json),
|
||||
pretty_print_(pretty_print) {
|
||||
DCHECK(json);
|
||||
}
|
||||
|
||||
void JSONWriter::BuildJSONString(const Value* const node, int depth) {
|
||||
switch(node->GetType()) {
|
||||
case Value::TYPE_NULL:
|
||||
json_string_->append("null");
|
||||
break;
|
||||
|
||||
case Value::TYPE_BOOLEAN:
|
||||
{
|
||||
bool value;
|
||||
bool result = node->GetAsBoolean(&value);
|
||||
DCHECK(result);
|
||||
json_string_->append(value ? "true" : "false");
|
||||
break;
|
||||
}
|
||||
|
||||
case Value::TYPE_INTEGER:
|
||||
{
|
||||
int value;
|
||||
bool result = node->GetAsInteger(&value);
|
||||
DCHECK(result);
|
||||
StringAppendF(json_string_, "%d", value);
|
||||
break;
|
||||
}
|
||||
|
||||
case Value::TYPE_REAL:
|
||||
{
|
||||
double value;
|
||||
bool result = node->GetAsReal(&value);
|
||||
DCHECK(result);
|
||||
std::string real = DoubleToString(value);
|
||||
// Ensure that the number has a .0 if there's no decimal or 'e'. This
|
||||
// makes sure that when we read the JSON back, it's interpreted as a
|
||||
// real rather than an int.
|
||||
if (real.find('.') == std::string::npos &&
|
||||
real.find('e') == std::string::npos &&
|
||||
real.find('E') == std::string::npos) {
|
||||
real.append(".0");
|
||||
}
|
||||
// The JSON spec requires that non-integer values in the range (-1,1)
|
||||
// have a zero before the decimal point - ".52" is not valid, "0.52" is.
|
||||
if (real[0] == '.') {
|
||||
real.insert(0, "0");
|
||||
} else if (real.length() > 1 && real[0] == '-' && real[1] == '.') {
|
||||
// "-.1" bad "-0.1" good
|
||||
real.insert(1, "0");
|
||||
}
|
||||
json_string_->append(real);
|
||||
break;
|
||||
}
|
||||
|
||||
case Value::TYPE_STRING:
|
||||
{
|
||||
std::wstring value;
|
||||
bool result = node->GetAsString(&value);
|
||||
DCHECK(result);
|
||||
AppendQuotedString(value);
|
||||
break;
|
||||
}
|
||||
|
||||
case Value::TYPE_LIST:
|
||||
{
|
||||
json_string_->append("[");
|
||||
if (pretty_print_)
|
||||
json_string_->append(" ");
|
||||
|
||||
const ListValue* list = static_cast<const ListValue*>(node);
|
||||
for (size_t i = 0; i < list->GetSize(); ++i) {
|
||||
if (i != 0) {
|
||||
json_string_->append(",");
|
||||
if (pretty_print_)
|
||||
json_string_->append(" ");
|
||||
}
|
||||
|
||||
Value* value = NULL;
|
||||
bool result = list->Get(i, &value);
|
||||
DCHECK(result);
|
||||
BuildJSONString(value, depth);
|
||||
}
|
||||
|
||||
if (pretty_print_)
|
||||
json_string_->append(" ");
|
||||
json_string_->append("]");
|
||||
break;
|
||||
}
|
||||
|
||||
case Value::TYPE_DICTIONARY:
|
||||
{
|
||||
json_string_->append("{");
|
||||
if (pretty_print_)
|
||||
json_string_->append(kPrettyPrintLineEnding);
|
||||
|
||||
const DictionaryValue* dict =
|
||||
static_cast<const DictionaryValue*>(node);
|
||||
for (DictionaryValue::key_iterator key_itr = dict->begin_keys();
|
||||
key_itr != dict->end_keys();
|
||||
++key_itr) {
|
||||
|
||||
if (key_itr != dict->begin_keys()) {
|
||||
json_string_->append(",");
|
||||
if (pretty_print_)
|
||||
json_string_->append(kPrettyPrintLineEnding);
|
||||
}
|
||||
|
||||
Value* value = NULL;
|
||||
bool result = dict->Get(*key_itr, &value);
|
||||
DCHECK(result);
|
||||
|
||||
if (pretty_print_)
|
||||
IndentLine(depth + 1);
|
||||
AppendQuotedString(*key_itr);
|
||||
if (pretty_print_) {
|
||||
json_string_->append(": ");
|
||||
} else {
|
||||
json_string_->append(":");
|
||||
}
|
||||
BuildJSONString(value, depth + 1);
|
||||
}
|
||||
|
||||
if (pretty_print_) {
|
||||
json_string_->append(kPrettyPrintLineEnding);
|
||||
IndentLine(depth);
|
||||
json_string_->append("}");
|
||||
} else {
|
||||
json_string_->append("}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// TODO(jhughes): handle TYPE_BINARY
|
||||
NOTREACHED() << "unknown json type";
|
||||
}
|
||||
}
|
||||
|
||||
void JSONWriter::AppendQuotedString(const std::wstring& str) {
|
||||
string_escape::JavascriptDoubleQuote(WideToUTF16Hack(str), true,
|
||||
json_string_);
|
||||
}
|
||||
|
||||
void JSONWriter::IndentLine(int depth) {
|
||||
// It may be faster to keep an indent string so we don't have to keep
|
||||
// reallocating.
|
||||
json_string_->append(std::string(depth * 3, ' '));
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_JSON_WRITER_H_
|
||||
#define BASE_JSON_WRITER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
|
||||
class Value;
|
||||
|
||||
class JSONWriter {
|
||||
public:
|
||||
// Given a root node, generates a JSON string and puts it into |json|.
|
||||
// If |pretty_print| is true, return a slightly nicer formated json string
|
||||
// (pads with whitespace to help readability). If |pretty_print| is false,
|
||||
// we try to generate as compact a string as possible.
|
||||
// TODO(tc): Should we generate json if it would be invalid json (e.g.,
|
||||
// |node| is not a DictionaryValue/ListValue or if there are inf/-inf float
|
||||
// values)?
|
||||
static void Write(const Value* const node, bool pretty_print,
|
||||
std::string* json);
|
||||
|
||||
private:
|
||||
JSONWriter(bool pretty_print, std::string* json);
|
||||
|
||||
// Called recursively to build the JSON string. Whe completed, value is
|
||||
// json_string_ will contain the JSON.
|
||||
void BuildJSONString(const Value* const node, int depth);
|
||||
|
||||
// Appends a quoted, escaped, version of str to json_string_.
|
||||
void AppendQuotedString(const std::wstring& str);
|
||||
|
||||
// Adds space to json_string_ for the indent level.
|
||||
void IndentLine(int depth);
|
||||
|
||||
// Where we write JSON data as we generate it.
|
||||
std::string* json_string_;
|
||||
|
||||
bool pretty_print_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(JSONWriter);
|
||||
};
|
||||
|
||||
#endif // BASE_JSON_WRITER_H_
|
@ -1,68 +0,0 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "base/json_writer.h"
|
||||
#include "base/values.h"
|
||||
|
||||
TEST(JSONWriterTest, Writing) {
|
||||
// Test null
|
||||
Value* root = Value::CreateNullValue();
|
||||
std::string output_js;
|
||||
JSONWriter::Write(root, false, &output_js);
|
||||
ASSERT_EQ("null", output_js);
|
||||
delete root;
|
||||
|
||||
// Test empty dict
|
||||
root = new DictionaryValue;
|
||||
JSONWriter::Write(root, false, &output_js);
|
||||
ASSERT_EQ("{}", output_js);
|
||||
delete root;
|
||||
|
||||
// Test empty list
|
||||
root = new ListValue;
|
||||
JSONWriter::Write(root, false, &output_js);
|
||||
ASSERT_EQ("[]", output_js);
|
||||
delete root;
|
||||
|
||||
// Test Real values should always have a decimal or an 'e'.
|
||||
root = Value::CreateRealValue(1.0);
|
||||
JSONWriter::Write(root, false, &output_js);
|
||||
ASSERT_EQ("1.0", output_js);
|
||||
delete root;
|
||||
|
||||
// Test Real values in the the range (-1, 1) must have leading zeros
|
||||
root = Value::CreateRealValue(0.2);
|
||||
JSONWriter::Write(root, false, &output_js);
|
||||
ASSERT_EQ("0.2", output_js);
|
||||
delete root;
|
||||
|
||||
// Test Real values in the the range (-1, 1) must have leading zeros
|
||||
root = Value::CreateRealValue(-0.8);
|
||||
JSONWriter::Write(root, false, &output_js);
|
||||
ASSERT_EQ("-0.8", output_js);
|
||||
delete root;
|
||||
|
||||
// Writer unittests like empty list/dict nesting,
|
||||
// list list nesting, etc.
|
||||
DictionaryValue root_dict;
|
||||
ListValue* list = new ListValue;
|
||||
root_dict.Set(L"list", list);
|
||||
DictionaryValue* inner_dict = new DictionaryValue;
|
||||
list->Append(inner_dict);
|
||||
inner_dict->SetInteger(L"inner int", 10);
|
||||
ListValue* inner_list = new ListValue;
|
||||
list->Append(inner_list);
|
||||
list->Append(Value::CreateBooleanValue(true));
|
||||
|
||||
JSONWriter::Write(&root_dict, false, &output_js);
|
||||
ASSERT_EQ("{\"list\":[{\"inner int\":10},[],true]}", output_js);
|
||||
JSONWriter::Write(&root_dict, true, &output_js);
|
||||
ASSERT_EQ("{\r\n"
|
||||
" \"list\": [ {\r\n"
|
||||
" \"inner int\": 10\r\n"
|
||||
" }, [ ], true ]\r\n"
|
||||
"}\r\n",
|
||||
output_js);
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CHROME_COMMON_ACCESSIBILITY_TYPES_H_
|
||||
#define CHROME_COMMON_ACCESSIBILITY_TYPES_H_
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// AccessibilityTypes
|
||||
//
|
||||
// Provides enumerations used to preserve platform-independence in accessibility
|
||||
// functions used in various Views, both in Browser\Views and Views.
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
class AccessibilityTypes {
|
||||
public:
|
||||
// This defines an enumeration of the supported accessibility roles in our
|
||||
// Views (e.g. used in View::GetAccessibleRole). Any interface using roles
|
||||
// must provide a conversion to its own roles (see e.g.
|
||||
// ViewAccessibility::get_accRole and ViewAccessibility::MSAARole).
|
||||
enum Role {
|
||||
ROLE_APPLICATION,
|
||||
ROLE_BUTTONDROPDOWN,
|
||||
ROLE_CLIENT,
|
||||
ROLE_GROUPING,
|
||||
ROLE_PAGETAB,
|
||||
ROLE_PUSHBUTTON,
|
||||
ROLE_TEXT,
|
||||
ROLE_TOOLBAR
|
||||
};
|
||||
|
||||
// This defines an enumeration of the supported accessibility roles in our
|
||||
// Views (e.g. used in View::GetAccessibleState). Any interface using roles
|
||||
// must provide a conversion to its own roles (see e.g.
|
||||
// ViewAccessibility::get_accState and ViewAccessibility::MSAAState).
|
||||
enum State {
|
||||
STATE_HASPOPUP,
|
||||
STATE_READONLY
|
||||
};
|
||||
|
||||
private:
|
||||
// Do not instantiate this class.
|
||||
AccessibilityTypes() {}
|
||||
~AccessibilityTypes() {}
|
||||
};
|
||||
|
||||
#endif // CHROME_COMMON_ACCESSIBILITY_TYPES_H_
|
@ -1,84 +0,0 @@
|
||||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "chrome/common/app_cache/app_cache_context_impl.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "chrome/common/render_messages.h"
|
||||
#include "chrome/common/child_thread.h"
|
||||
#include "googleurl/src/gurl.h"
|
||||
|
||||
IDMap<AppCacheContextImpl> AppCacheContextImpl::all_contexts;
|
||||
|
||||
// static
|
||||
AppCacheContextImpl* AppCacheContextImpl::FromContextId(int id) {
|
||||
return all_contexts.Lookup(id);
|
||||
}
|
||||
|
||||
AppCacheContextImpl::AppCacheContextImpl(IPC::Message::Sender *sender)
|
||||
: context_id_(kNoAppCacheContextId),
|
||||
app_cache_id_(kUnknownAppCacheId),
|
||||
pending_select_request_id_(0),
|
||||
sender_(sender) {
|
||||
DCHECK(sender_);
|
||||
}
|
||||
|
||||
AppCacheContextImpl::~AppCacheContextImpl() {
|
||||
UnInitializeContext();
|
||||
}
|
||||
|
||||
void AppCacheContextImpl::Initialize(ContextType context_type,
|
||||
WebAppCacheContext *parent) {
|
||||
DCHECK(context_id_ == kNoAppCacheContextId);
|
||||
DCHECK(((context_type == MAIN_FRAME) && !parent) ||
|
||||
((context_type != MAIN_FRAME) && parent));
|
||||
|
||||
context_id_ = all_contexts.Add(this);
|
||||
CHECK(context_id_ != kNoAppCacheContextId);
|
||||
|
||||
sender_->Send(new AppCacheMsg_ContextCreated(context_type,
|
||||
context_id_,
|
||||
parent ? parent->GetContextID()
|
||||
: kNoAppCacheContextId));
|
||||
}
|
||||
|
||||
void AppCacheContextImpl::UnInitializeContext() {
|
||||
if (context_id_ != kNoAppCacheContextId) {
|
||||
sender_->Send(new AppCacheMsg_ContextDestroyed(context_id_));
|
||||
all_contexts.Remove(context_id_);
|
||||
context_id_ = kNoAppCacheContextId;
|
||||
}
|
||||
}
|
||||
|
||||
void AppCacheContextImpl::SelectAppCacheWithoutManifest(
|
||||
const GURL &document_url,
|
||||
int64_t cache_document_was_loaded_from) {
|
||||
DCHECK(context_id_ != kNoAppCacheContextId);
|
||||
app_cache_id_ = kUnknownAppCacheId; // unknown until we get a response
|
||||
sender_->Send(new AppCacheMsg_SelectAppCache(
|
||||
context_id_, ++pending_select_request_id_,
|
||||
document_url, cache_document_was_loaded_from,
|
||||
GURL::EmptyGURL()));
|
||||
}
|
||||
|
||||
void AppCacheContextImpl::SelectAppCacheWithManifest(
|
||||
const GURL &document_url,
|
||||
int64_t cache_document_was_loaded_from,
|
||||
const GURL &manifest_url) {
|
||||
DCHECK(context_id_ != kNoAppCacheContextId);
|
||||
app_cache_id_ = kUnknownAppCacheId; // unknown until we get a response
|
||||
sender_->Send(new AppCacheMsg_SelectAppCache(
|
||||
context_id_, ++pending_select_request_id_,
|
||||
document_url, cache_document_was_loaded_from,
|
||||
manifest_url));
|
||||
}
|
||||
|
||||
void AppCacheContextImpl::OnAppCacheSelected(int select_request_id,
|
||||
int64_t app_cache_id) {
|
||||
if (select_request_id == pending_select_request_id_) {
|
||||
DCHECK(app_cache_id_ == kUnknownAppCacheId);
|
||||
DCHECK(app_cache_id != kUnknownAppCacheId);
|
||||
app_cache_id_ = app_cache_id;
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CHROME_COMMON_APP_CACHE_APP_CACHE_CONTEXT_IMPL_H_
|
||||
#define CHROME_COMMON_APP_CACHE_APP_CACHE_CONTEXT_IMPL_H_
|
||||
|
||||
#include "base/id_map.h"
|
||||
#include "chrome/common/ipc_message.h"
|
||||
#include "webkit/glue/webappcachecontext.h"
|
||||
|
||||
// A concrete implemenation of WebAppCacheContext for use in a child process.
|
||||
class AppCacheContextImpl : public WebAppCacheContext {
|
||||
public:
|
||||
// Returns the context having given id or NULL if there is no such context.
|
||||
static AppCacheContextImpl* FromContextId(int id);
|
||||
|
||||
AppCacheContextImpl(IPC::Message::Sender* sender);
|
||||
virtual ~AppCacheContextImpl();
|
||||
|
||||
// WebAppCacheContext implementation
|
||||
virtual int GetContextID() { return context_id_; }
|
||||
virtual int64_t GetAppCacheID() { return app_cache_id_; }
|
||||
virtual void Initialize(WebAppCacheContext::ContextType context_type,
|
||||
WebAppCacheContext* opt_parent);
|
||||
virtual void SelectAppCacheWithoutManifest(
|
||||
const GURL& document_url,
|
||||
int64_t cache_document_was_loaded_from);
|
||||
virtual void SelectAppCacheWithManifest(
|
||||
const GURL& document_url,
|
||||
int64_t cache_document_was_loaded_from,
|
||||
const GURL& manifest_url);
|
||||
|
||||
// Called by AppCacheDispatcher when the browser has selected an appcache.
|
||||
void OnAppCacheSelected(int select_request_id, int64_t app_cache_id);
|
||||
|
||||
private:
|
||||
void UnInitializeContext();
|
||||
|
||||
int context_id_;
|
||||
int64_t app_cache_id_;
|
||||
int pending_select_request_id_;
|
||||
IPC::Message::Sender* sender_;
|
||||
|
||||
static IDMap<AppCacheContextImpl> all_contexts;
|
||||
};
|
||||
|
||||
#endif // CHROME_COMMON_APP_CACHE_APP_CACHE_CONTEXT_IMPL_H_
|
@ -1,26 +0,0 @@
|
||||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "chrome/common/app_cache/app_cache_dispatcher.h"
|
||||
|
||||
#include "chrome/common/app_cache/app_cache_context_impl.h"
|
||||
#include "chrome/common/render_messages.h"
|
||||
|
||||
bool AppCacheDispatcher::OnMessageReceived(const IPC::Message& msg) {
|
||||
bool handled = true;
|
||||
IPC_BEGIN_MESSAGE_MAP(AppCacheDispatcher, msg)
|
||||
IPC_MESSAGE_HANDLER(AppCacheMsg_AppCacheSelected, OnAppCacheSelected)
|
||||
IPC_MESSAGE_UNHANDLED(handled = false)
|
||||
IPC_END_MESSAGE_MAP()
|
||||
return handled;
|
||||
}
|
||||
|
||||
void AppCacheDispatcher::OnAppCacheSelected(int context_id,
|
||||
int select_request_id,
|
||||
int64_t app_cache_id) {
|
||||
AppCacheContextImpl *context = AppCacheContextImpl::FromContextId(context_id);
|
||||
if (context) {
|
||||
context->OnAppCacheSelected(select_request_id, app_cache_id);
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CHROME_COMMON_APP_CACHE_APP_CACHE_DISPATCHER_H_
|
||||
#define CHROME_COMMON_APP_CACHE_APP_CACHE_DISPATCHER_H_
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "chrome/common/ipc_message.h"
|
||||
|
||||
// Dispatches app cache related messages sent to a child process from the
|
||||
// main browser process. There is one instance per child process. Messages
|
||||
// are dispatched on the main child thread. The ChildThread base class
|
||||
// creates an instance and delegates calls to it.
|
||||
class AppCacheDispatcher {
|
||||
public:
|
||||
bool OnMessageReceived(const IPC::Message& msg);
|
||||
|
||||
private:
|
||||
// AppCacheContextImpl related messages
|
||||
void OnAppCacheSelected(int context_id,
|
||||
int select_request_id,
|
||||
int64_t app_cache_id);
|
||||
};
|
||||
|
||||
#endif // CHROME_COMMON_APP_CACHE_APP_CACHE_DISPATCHER_H_
|
@ -1,60 +0,0 @@
|
||||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "chrome/common/app_cache/app_cache_dispatcher_host.h"
|
||||
|
||||
#include "chrome/common/render_messages.h"
|
||||
|
||||
AppCacheDispatcherHost::~AppCacheDispatcherHost() {
|
||||
if (sender_) {
|
||||
// TODO(michaeln): plumb to request_context_->app_cache_service
|
||||
// to remove contexts for the child process that is going away.
|
||||
}
|
||||
}
|
||||
|
||||
void AppCacheDispatcherHost::Initialize(IPC::Message::Sender* sender) {
|
||||
DCHECK(sender);
|
||||
sender_ = sender;
|
||||
// TODO(michaeln): plumb to request_context_->app_cache_service to
|
||||
// tell it about this child process coming into existance
|
||||
}
|
||||
|
||||
bool AppCacheDispatcherHost::OnMessageReceived(const IPC::Message& msg,
|
||||
bool *msg_ok) {
|
||||
DCHECK(sender_);
|
||||
*msg_ok = true;
|
||||
bool handled = true;
|
||||
IPC_BEGIN_MESSAGE_MAP_EX(AppCacheDispatcherHost, msg, *msg_ok)
|
||||
IPC_MESSAGE_HANDLER(AppCacheMsg_ContextCreated, OnContextCreated);
|
||||
IPC_MESSAGE_HANDLER(AppCacheMsg_ContextDestroyed, OnContextDestroyed);
|
||||
IPC_MESSAGE_HANDLER(AppCacheMsg_SelectAppCache, OnSelectAppCache);
|
||||
IPC_MESSAGE_UNHANDLED(handled = false)
|
||||
IPC_END_MESSAGE_MAP_EX()
|
||||
return handled;
|
||||
}
|
||||
|
||||
void AppCacheDispatcherHost::OnContextCreated(
|
||||
WebAppCacheContext::ContextType context_type,
|
||||
int context_id,
|
||||
int opt_parent_id) {
|
||||
// TODO(michaeln): implement me, plumb to request_context->app_cache_service
|
||||
DCHECK(context_id != WebAppCacheContext::kNoAppCacheContextId);
|
||||
}
|
||||
|
||||
void AppCacheDispatcherHost::OnContextDestroyed(int context_id) {
|
||||
// TODO(michaeln): implement me, plumb to request_context->app_cache_service
|
||||
DCHECK(context_id != WebAppCacheContext::kNoAppCacheContextId);
|
||||
}
|
||||
|
||||
void AppCacheDispatcherHost::OnSelectAppCache(
|
||||
int context_id,
|
||||
int select_request_id,
|
||||
const GURL& document_url,
|
||||
int64_t cache_document_was_loaded_from,
|
||||
const GURL& opt_manifest_url) {
|
||||
// TODO(michaeln): implement me, plumb to request_context->app_cache_service
|
||||
DCHECK(context_id != WebAppCacheContext::kNoAppCacheContextId);
|
||||
Send(new AppCacheMsg_AppCacheSelected(context_id, select_request_id,
|
||||
WebAppCacheContext::kNoAppCacheId));
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CHROME_COMMON_APP_CACHE_APP_CACHE_DISPATCHER_HOST_H_
|
||||
#define CHROME_COMMON_APP_CACHE_APP_CACHE_DISPATCHER_HOST_H_
|
||||
|
||||
#include "base/id_map.h"
|
||||
#include "chrome/common/ipc_message.h"
|
||||
#include "webkit/glue/webappcachecontext.h"
|
||||
|
||||
class GURL;
|
||||
|
||||
// Handles app cache related messages sent to the main browser process from
|
||||
// its child processes. There is a distinct host for each child process.
|
||||
// Messages are handled on the IO thread. The ResourceMessageFilter creates
|
||||
// an instance and delegates calls to it.
|
||||
class AppCacheDispatcherHost {
|
||||
public:
|
||||
AppCacheDispatcherHost() : sender_(NULL) {}
|
||||
~AppCacheDispatcherHost();
|
||||
void Initialize(IPC::Message::Sender* sender);
|
||||
bool OnMessageReceived(const IPC::Message& msg, bool* msg_is_ok);
|
||||
|
||||
private:
|
||||
// AppCacheContextImpl related messages
|
||||
void OnContextCreated(WebAppCacheContext::ContextType context_type,
|
||||
int context_id, int opt_parent_id);
|
||||
void OnContextDestroyed(int context_id);
|
||||
void OnSelectAppCache(int context_id,
|
||||
int select_request_id,
|
||||
const GURL& document_url,
|
||||
int64_t cache_document_was_loaded_from,
|
||||
const GURL& opt_manifest_url);
|
||||
|
||||
bool Send(IPC::Message* msg) {
|
||||
return sender_->Send(msg);
|
||||
}
|
||||
|
||||
IPC::Message::Sender* sender_;
|
||||
};
|
||||
|
||||
#endif // CHROME_COMMON_APP_CACHE_APP_CACHE_DISPATCHER_HOST_H_
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user