diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-01-09 17:51:23 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-01-09 17:51:23 -0800 |
commit | e933faefa1e899dbd5bf371f499cc682aff46c83 (patch) | |
tree | 8fb31ff5c9a41aec9912d0253be7ef0445e2f58a /WebKit/android | |
parent | 1cbdecfa9fc428ac2d8aca0fa91c9580b3d57353 (diff) | |
download | external_webkit-e933faefa1e899dbd5bf371f499cc682aff46c83.zip external_webkit-e933faefa1e899dbd5bf371f499cc682aff46c83.tar.gz external_webkit-e933faefa1e899dbd5bf371f499cc682aff46c83.tar.bz2 |
auto import from //branches/cupcake/...@125939
Diffstat (limited to 'WebKit/android')
24 files changed, 1505 insertions, 276 deletions
diff --git a/WebKit/android/jni/WebCoreFrameBridge.cpp b/WebKit/android/jni/WebCoreFrameBridge.cpp index 9f55134..a173305 100644 --- a/WebKit/android/jni/WebCoreFrameBridge.cpp +++ b/WebKit/android/jni/WebCoreFrameBridge.cpp @@ -70,6 +70,7 @@ #include "WebIconDatabase.h" #include "WebFrameView.h" #include "WebViewCore.h" +#include "wds/DebugServer.h" #include <runtime_root.h> #include <runtime_object.h> @@ -736,6 +737,9 @@ static void CreateFrame(JNIEnv* env, jobject obj, jobject javaview, jobject jAss // Create a Frame and the page holds its reference WebCore::Frame* frame = WebCore::Frame::create(page, NULL, loaderC).get(); loaderC->setFrame(frame); +#if ENABLE(WDS) + WDS::server()->addFrame(frame); +#endif // Create a WebViewCore to access the Java WebViewCore associated with this page WebViewCore* webViewCore = new WebViewCore(env, javaview, frame); @@ -793,6 +797,9 @@ static void DestroyFrame(JNIEnv* env, jobject obj) view->deref(); SET_NATIVE_FRAME(env, obj, 0); +#if ENABLE(WDS) + WDS::server()->removeFrame(pFrame); +#endif } static void LoadUrl(JNIEnv *env, jobject obj, jstring url) diff --git a/WebKit/android/jni/WebViewCore.cpp b/WebKit/android/jni/WebViewCore.cpp index 363f2be..42a0043 100644 --- a/WebKit/android/jni/WebViewCore.cpp +++ b/WebKit/android/jni/WebViewCore.cpp @@ -1156,10 +1156,22 @@ bool WebViewCore::finalKitFocus(WebCore::Frame* frame, WebCore::Node* node, } if (!node->isTextNode()) static_cast<WebCore::Element*>(node)->focus(false); - //setFocus on things that WebCore doesn't recognize as supporting focus - //for instance, if there is an onclick element that does not support focus + if (node->document()->focusedNode() != node) { + // This happens when Element::focus() fails as we may try to set the + // focus to a node which WebCore doesn't recognize as a focusable node. + // So we need to do some extra work, as it does in Element::focus(), + // besides calling Document::setFocusedNode. + if (oldFocusNode) { + // copied from clearSelectionIfNeeded in FocusController.cpp + WebCore::SelectionController* s = oldDoc->frame()->selection(); + if (!s->isNone()) + s->clear(); + } + //setFocus on things that WebCore doesn't recognize as supporting focus + //for instance, if there is an onclick element that does not support focus + node->document()->setFocusedNode(node); + } DBG_NAV_LOGD("setFocusedNode node=%p", node); - node->document()->setFocusedNode(node); m_lastFocused = node; m_lastFocusedBounds = node->getRect(); return true; @@ -1527,7 +1539,7 @@ public: WebCore::HTMLOptionElement* option; for (int i = 0; i < count; i++) { option = static_cast<WebCore::HTMLOptionElement*>( - m_select->item(array[m_select->listToOptionIndex(i)])); + m_select->item(m_select->listToOptionIndex(array[i]))); option->setSelected(true); } m_viewImpl->contentInvalidate(m_select->getRect()); @@ -1609,78 +1621,40 @@ void WebViewCore::listBoxRequest(WebCoreReply* reply, const uint16_t** labels, s m_popupReply = reply; } -bool WebViewCore::keyUp(KeyCode keyCode, int keyVal) +bool WebViewCore::key(int keyCode, UChar32 unichar, int repeatCount, bool isShift, bool isAlt, bool isDown) { - DBG_NAV_LOGD("keyCode=%d", keyCode); - bool keyHandled = false; + DBG_NAV_LOGD("key: keyCode=%d", keyCode); + + WebCore::EventHandler* eventHandler = m_mainFrame->eventHandler(); WebCore::Node* focusNode = m_mainFrame->getCacheBuilder().currentFocus(); if (focusNode) { - WebCore::Frame* focusFrame = focusNode->document()->frame(); - switch (keyCode) { - case kKeyCodeNewline: - case kKeyCodeDpadCenter: { - focusFrame->loader()->resetMultipleFormSubmissionProtection(); - WebFrame* webFrame = WebFrame::getWebFrame(m_mainFrame); - webFrame->setUserInitiatedClick(true); - if ((focusNode->hasTagName(WebCore::HTMLNames::inputTag) && - ((WebCore::HTMLInputElement*)focusNode)->isTextField()) || - focusNode->hasTagName(WebCore::HTMLNames::textareaTag)) { - // Create the key down event. - WebCore::PlatformKeyboardEvent keydown(keyCode, keyVal, - WebCore::PlatformKeyboardEvent::KeyDown, 0, - NO_MODIFIER_KEYS); - // Create the key up event. - WebCore::PlatformKeyboardEvent keyup(keyCode, keyVal, - WebCore::PlatformKeyboardEvent::KeyUp, 0, - NO_MODIFIER_KEYS); - // Send both events. - keyHandled = focusFrame->eventHandler()->keyEvent(keydown); - keyHandled |= focusFrame->eventHandler()->keyEvent(keyup); - } else { - keyHandled = handleMouseClick(focusFrame, focusNode); - } - webFrame->setUserInitiatedClick(false); - break; - } - default: - keyHandled = false; - } + eventHandler = focusNode->document()->frame()->eventHandler(); } - m_blockFocusChange = false; - return keyHandled; + + int mods = 0; // PlatformKeyboardEvent::ModifierKey + if (isShift) { + mods |= WebCore::PlatformKeyboardEvent::ShiftKey; + } + if (isAlt) { + mods |= WebCore::PlatformKeyboardEvent::AltKey; + } + WebCore::PlatformKeyboardEvent evt(keyCode, unichar, + isDown ? WebCore::PlatformKeyboardEvent::KeyDown : WebCore::PlatformKeyboardEvent::KeyUp, + repeatCount, static_cast<WebCore::PlatformKeyboardEvent::ModifierKey> (mods)); + return eventHandler->keyEvent(evt); } -bool WebViewCore::sendKeyToFocusNode(int keyCode, UChar32 unichar, - int repeatCount, bool isShift, bool isAlt, KeyAction action) { +bool WebViewCore::click() { + bool keyHandled = false; WebCore::Node* focusNode = m_mainFrame->getCacheBuilder().currentFocus(); if (focusNode) { - WebCore::Frame* focusFrame = focusNode->document()->frame(); - WebCore::PlatformKeyboardEvent::Type type; - switch (action) { - case DownKeyAction: - type = WebCore::PlatformKeyboardEvent::KeyDown; - break; - case UpKeyAction: - type = WebCore::PlatformKeyboardEvent::KeyUp; - break; - default: - SkDebugf("---- unexpected KeyAction %d\n", action); - return false; - } - - int mods = 0; // PlatformKeyboardEvent::ModifierKey - if (isShift) { - mods |= WebCore::PlatformKeyboardEvent::ShiftKey; - } - if (isAlt) { - mods |= WebCore::PlatformKeyboardEvent::AltKey; - } - - WebCore::PlatformKeyboardEvent evt(keyCode, unichar, type, repeatCount, - static_cast<WebCore::PlatformKeyboardEvent::ModifierKey>(mods)); - return focusFrame->eventHandler()->keyEvent(evt); + WebFrame::getWebFrame(m_mainFrame)->setUserInitiatedClick(true); + keyHandled = handleMouseClick(focusNode->document()->frame(), focusNode); + WebFrame::getWebFrame(m_mainFrame)->setUserInitiatedClick(false); } - return false; + // match in setFinalFocus() + m_blockFocusChange = false; + return keyHandled; } bool WebViewCore::handleTouchEvent(int action, int x, int y) @@ -2008,29 +1982,27 @@ static void SetGlobalBounds(JNIEnv *env, jobject obj, jint x, jint y, jint h, viewImpl->setGlobalBounds(x, y, h, v); } -static jboolean KeyUp(JNIEnv *env, jobject obj, jint key, jint val) +static jboolean Key(JNIEnv *env, jobject obj, jint keyCode, jint unichar, + jint repeatCount, jboolean isShift, jboolean isAlt, jboolean isDown) { #ifdef ANDROID_INSTRUMENT TimeCounterWV counter; #endif -// LOGV("webviewcore::nativeKeyUp(%u)\n", (unsigned)key); WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); - LOG_ASSERT(viewImpl, "viewImpl not set in nativeKeyUp"); - return viewImpl->keyUp((KeyCode)key, val); + LOG_ASSERT(viewImpl, "viewImpl not set in Key"); + + return viewImpl->key(keyCode, unichar, repeatCount, isShift, isAlt, isDown); } -static bool SendKeyToFocusNode(JNIEnv *env, jobject jwebviewcore, - jint keyCode, jint unichar, jint repeatCount, - jboolean isShift, jboolean isAlt, jint action) +static jboolean Click(JNIEnv *env, jobject obj) { #ifdef ANDROID_INSTRUMENT TimeCounterWV counter; #endif + WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(viewImpl, "viewImpl not set in Click"); - WebViewCore* viewImpl = GET_NATIVE_VIEW(env, jwebviewcore); - LOG_ASSERT(viewImpl, "viewImpl not set in SendKeyToFocusNode"); - return viewImpl->sendKeyToFocusNode((KeyCode)keyCode, unichar, repeatCount, - isShift, isAlt, static_cast<WebViewCore::KeyAction>(action)); + return viewImpl->click(); } static void DeleteSelection(JNIEnv *env, jobject obj, @@ -2390,9 +2362,10 @@ static JNINativeMethod gJavaWebViewCoreMethods[] = { (void*) CopyContentToPicture }, { "nativeDrawContent", "(Landroid/graphics/Canvas;I)Z", (void*) DrawContent } , - { "nativeKeyUp", "(II)Z", - (void*) KeyUp }, - { "nativeSendKeyToFocusNode", "(IIIZZI)Z", (void*) SendKeyToFocusNode }, + { "nativeKey", "(IIIZZZ)Z", + (void*) Key }, + { "nativeClick", "()Z", + (void*) Click }, { "nativeSendListBoxChoices", "([ZI)V", (void*) SendListBoxChoices }, { "nativeSendListBoxChoice", "(I)V", diff --git a/WebKit/android/jni/WebViewCore.h b/WebKit/android/jni/WebViewCore.h index 8a073c2..a6c44f8 100644 --- a/WebKit/android/jni/WebViewCore.h +++ b/WebKit/android/jni/WebViewCore.h @@ -189,24 +189,15 @@ namespace android { void setSizeScreenWidthAndScale(int width, int height, int screenWidth, int scale); /** - * Handle keyDown events from Java. - * @param keyCode The key pressed. + * Handle key events from Java. * @return Whether keyCode was handled by this class. */ - bool keyUp(KeyCode keyCode, int keyValue); + bool key(int keyCode, UChar32 unichar, int repeatCount, bool isShift, bool isAlt, bool isDown); - // These need to be lock-step with the KEY_ACTION values in - // WebViewCore.java - enum KeyAction { - DownKeyAction = 0, - UpKeyAction = 1 - }; /** - * Send the key event to the focus node (if there is one). Return true - * if there is a node, and it claims to have handled the event. + * Handle (mouse) click event from Java */ - bool sendKeyToFocusNode(int code, UChar32 unichar, int repeatCount, - bool isShift, bool isAlt, KeyAction); + bool click(); /** * Handle touch event diff --git a/WebKit/android/nav/CacheBuilder.cpp b/WebKit/android/nav/CacheBuilder.cpp index da32898..69d0a06 100644 --- a/WebKit/android/nav/CacheBuilder.cpp +++ b/WebKit/android/nav/CacheBuilder.cpp @@ -27,6 +27,7 @@ #include "HTMLAreaElement.h" #include "HTMLImageElement.h" #include "HTMLInputElement.h" +#include "HTMLMapElement.h" #include "HTMLNames.h" #include "HTMLOptionElement.h" #include "HTMLSelectElement.h" @@ -398,7 +399,7 @@ void CacheBuilder::Debug::groups() { properties.truncate(properties.length() - 3); IntRect rect = node->getRect(); if (node->hasTagName(HTMLNames::areaTag)) - rect = static_cast<HTMLAreaElement*>(node)->getAreaRect(); + rect = Builder(frame)->getAreaRect(static_cast<HTMLAreaElement*>(node)); char buffer[DEBUG_BUFFER_SIZE]; memset(buffer, 0, sizeof(buffer)); mBuffer = buffer; @@ -1004,12 +1005,20 @@ void CacheBuilder::BuildFrame(Frame* root, Frame* frame, RenderStyle* style = nodeRenderer->style(); if (style->visibility() == HIDDEN) continue; -#ifdef ANDROID_NAVIGATE_AREAMAPS if (nodeRenderer->isImage()) { // set all the area elements to have a link to their images RenderImage* image = static_cast<RenderImage*>(nodeRenderer); - image->setImageForAreaElements(); + HTMLMapElement* map = image->imageMap(); + if (map) { + Node* node; + for (node = map->firstChild(); node; + node = node->traverseNextNode(map)) { + if (!node->hasTagName(HTMLNames::areaTag)) + continue; + HTMLAreaElement* area = static_cast<HTMLAreaElement*>(node); + m_areaBoundsMap.set(area, image); + } + } } -#endif isTransparent = style->hasBackground() == false; #ifdef ANDROID_CSS_TAP_HIGHLIGHT_COLOR hasFocusRing = style->tapHighlightColor().alpha() > 0; @@ -1043,10 +1052,9 @@ void CacheBuilder::BuildFrame(Frame* root, Frame* frame, IntRect absBounds; Node* lastChild = node->lastChild(); WTF::Vector<IntRect>* columns = NULL; -#ifdef ANDROID_NAVIGATE_AREAMAPS if (isArea) { HTMLAreaElement* area = static_cast<HTMLAreaElement*>(node); - bounds = area->getAreaRect(); + bounds = getAreaRect(area); bounds.move(globalOffsetX, globalOffsetY); absBounds = bounds; isUnclipped = true; // FIXME: areamaps require more effort to detect @@ -1054,7 +1062,6 @@ void CacheBuilder::BuildFrame(Frame* root, Frame* frame, takesFocus = true; goto keepNode; } -#endif if (nodeRenderer == NULL) continue; @@ -1183,6 +1190,9 @@ void CacheBuilder::BuildFrame(Frame* root, Frame* frame, if (isFocusable == false) { if (node->isEventTargetNode() == false) continue; + EventTargetNode* eventTargetNode = (EventTargetNode*) node; + if (eventTargetNode->disabled()) + continue; bool overOrOut = HasOverOrOut(node); bool hasTrigger = HasTriggerEvent(node); if (overOrOut == false && hasTrigger == false) @@ -1233,9 +1243,6 @@ void CacheBuilder::BuildFrame(Frame* root, Frame* frame, else clip.intersect(parentClip); hasClip = true; - DBG_NAV_LOGD("absBounds={%d,%d,%d,%d} parentClip={%d,%d,%d,%d}\n", - absBounds.x(), absBounds.y(), absBounds.width(), absBounds.height(), - parentClip.x(), parentClip.y(), parentClip.width(), parentClip.height()); } if (hasClip && cachedNode.clip(clip) == false) { cachedNode.setBounds(clip); @@ -2365,6 +2372,16 @@ void CacheBuilder::FindResetNumber(FindState* state) state->mStorePtr = state->mStore; } +IntRect CacheBuilder::getAreaRect(const HTMLAreaElement* area) const +{ + RenderImage* map = m_areaBoundsMap.get(area); + if (!map) + return IntRect(); + if (area->isDefault()) + return map->absoluteBoundingBoxRect(); + return area->getRect(map); +} + void CacheBuilder::GetGlobalOffset(Node* node, int* x, int * y) { GetGlobalOffset(node->document()->frame(), x, y); @@ -2459,13 +2476,11 @@ Node* CacheBuilder::findByCenter(int x, int y) const if (node->isTextNode()) continue; IntRect bounds; -#ifdef ANDROID_NAVIGATE_AREAMAPS if (node->hasTagName(HTMLNames::areaTag)) { HTMLAreaElement* area = static_cast<HTMLAreaElement*>(node); - bounds = area->getAreaRect(); + bounds = getAreaRect(area); bounds.move(globalOffsetX, globalOffsetY); } else -#endif bounds = node->getRect(); if (bounds.isEmpty()) continue; @@ -2775,12 +2790,8 @@ bool CacheBuilder::validNode(void* matchFrame, void* matchNode) const Node* node = frame->document(); while (node != NULL) { if (node == matchNode) { -#ifdef ANDROID_NAVIGATE_AREAMAPS const IntRect& rect = node->hasTagName(HTMLNames::areaTag) ? - static_cast<HTMLAreaElement*>(node)->getAreaRect() : node->getRect(); -#else - const IntRect& rect = node->getRect(); -#endif + getAreaRect(static_cast<HTMLAreaElement*>(node)) : node->getRect(); // Consider nodes with empty rects that are not at the origin // to be valid, since news.google.com has valid nodes like this if (rect.x() == 0 && rect.y() == 0 && rect.isEmpty()) diff --git a/WebKit/android/nav/CacheBuilder.h b/WebKit/android/nav/CacheBuilder.h index 075042e..4a424a9 100644 --- a/WebKit/android/nav/CacheBuilder.h +++ b/WebKit/android/nav/CacheBuilder.h @@ -37,10 +37,12 @@ namespace WebCore { class Document; class Frame; +class HTMLAreaElement; class InlineTextBox; class Node; class PlatformGraphicsContext; class RenderFlow; +class RenderImage; class RenderObject; class RenderLayer; class Text; @@ -193,6 +195,7 @@ private: static Frame* FrameAnd(CacheBuilder* focusNav); static Frame* FrameAnd(const CacheBuilder* focusNav); static CacheBuilder* Builder(Frame* ); + IntRect getAreaRect(const HTMLAreaElement* area) const; static Frame* HasFrame(Node* ); static bool HasOverOrOut(Node* ); static bool HasTriggerEvent(Node* ); @@ -208,6 +211,7 @@ private: Node* mLastKnownFocus; IntRect mLastKnownFocusBounds; android::CachedNodeType mAllowableTypes; + WTF::HashMap<const HTMLAreaElement* , RenderImage* > m_areaBoundsMap; #if DUMP_NAV_CACHE public: class Debug { diff --git a/WebKit/android/nav/FindCanvas.cpp b/WebKit/android/nav/FindCanvas.cpp index 5b6978c..60a7486 100644 --- a/WebKit/android/nav/FindCanvas.cpp +++ b/WebKit/android/nav/FindCanvas.cpp @@ -19,6 +19,31 @@ #include "SkRect.h" +// MatchInfo methods +//////////////////////////////////////////////////////////////////////////////// + +MatchInfo::MatchInfo() { + m_picture = 0; +} + +MatchInfo::~MatchInfo() { + m_picture->safeUnref(); +} + +MatchInfo::MatchInfo(const MatchInfo& src) { + m_location = src.m_location; + m_picture = src.m_picture; + m_picture->safeRef(); +} + +void MatchInfo::set(const SkRegion& region, SkPicture* pic) { + m_picture->safeUnref(); + m_location = region; + m_picture = pic; + SkASSERT(pic); + pic->ref(); +} + // GlyphSet methods //////////////////////////////////////////////////////////////////////////////// @@ -81,33 +106,17 @@ FindCanvas::FindCanvas(int width, int height, const UChar* lower, setBounder(&mBounder); mOutset = -SkIntToScalar(2); - mRegions = new WTF::Vector<SkRegion>(); -#if RECORD_MATCHES - mPicture = new SkPicture(); - mRecordingCanvas = mPicture->beginRecording(width, height); -#endif + mMatches = new WTF::Vector<MatchInfo>(); mWorkingIndex = 0; + mWorkingCanvas = 0; + mWorkingPicture = 0; } FindCanvas::~FindCanvas() { setBounder(NULL); /* Just in case getAndClear was not called. */ - delete mRegions; -#if RECORD_MATCHES - mPicture->unref(); -#endif -} - -// SkPaint.measureText is used to get a rectangle surrounding the specified -// text. It is a tighter bounds than we want. We want the height to account -// for the ascent and descent of the font, so that the rectangles will line up, -// regardless of the characters contained in them. -static void correctSize(const SkPaint& paint, SkRect& rect) -{ - SkPaint::FontMetrics fontMetrics; - paint.getFontMetrics(&fontMetrics); - rect.fTop = fontMetrics.fAscent; - rect.fBottom = fontMetrics.fDescent; + delete mMatches; + mWorkingPicture->safeUnref(); } // Each version of addMatch returns a rectangle for a match. @@ -117,21 +126,24 @@ SkRect FindCanvas::addMatchNormal(int index, const SkScalar pos[], SkScalar y) { const uint16_t* lineStart = glyphs - index; /* Use the original paint, since "text" is in glyphs */ - SkScalar before = paint.measureText(lineStart, index * sizeof(uint16_t), - NULL); + SkScalar before = paint.measureText(lineStart, index * sizeof(uint16_t), 0); SkRect rect; - int countInBytes = count << 1; - paint.measureText(glyphs, countInBytes, &rect); - correctSize(paint, rect); - rect.offset(pos[0] + before, y); - getTotalMatrix().mapRect(&rect); + rect.fLeft = pos[0] + before; + int countInBytes = count * sizeof(uint16_t); + rect.fRight = paint.measureText(glyphs, countInBytes, 0) + rect.fLeft; + SkPaint::FontMetrics fontMetrics; + paint.getFontMetrics(&fontMetrics); + SkScalar baseline = y; + rect.fTop = baseline + fontMetrics.fAscent; + rect.fBottom = baseline + fontMetrics.fDescent; + const SkMatrix& matrix = getTotalMatrix(); + matrix.mapRect(&rect); // Add the text to our picture. -#if RECORD_MATCHES - mRecordingCanvas->setMatrix(getTotalMatrix()); - SkPoint point; - matrix.mapXY(pos[0] + before, y, &point); - mRecordingCanvas->drawText(glyphs, countInBytes, point.fX, point.fY, paint); -#endif + SkCanvas* canvas = getWorkingCanvas(); + int saveCount = canvas->save(); + canvas->concat(matrix); + canvas->drawText(glyphs, countInBytes, pos[0] + before, y, paint); + canvas->restoreToCount(saveCount); return rect; } @@ -140,27 +152,31 @@ SkRect FindCanvas::addMatchPos(int index, const SkScalar xPos[], SkScalar /* y */) { SkRect r; r.setEmpty(); - const SkScalar* pos = &xPos[index << 1]; - int countInBytes = count << 1; + const SkPoint* temp = reinterpret_cast<const SkPoint*> (xPos); + const SkPoint* points = &temp[index]; + int countInBytes = count * sizeof(uint16_t); SkPaint::FontMetrics fontMetrics; paint.getFontMetrics(&fontMetrics); - for (int j = 0; j < countInBytes; j += 2) { + // Need to check each character individually, since the heights may be + // different. + for (int j = 0; j < count; j++) { SkRect bounds; - paint.getTextWidths(&(glyphs[j >> 1]), 2, NULL, &bounds); - bounds.fTop = fontMetrics.fAscent; - bounds.fBottom = fontMetrics.fDescent; - bounds.offset(pos[j], pos[j+1]); - /* Accumulate and then add the resulting rect to mRegions */ + bounds.fLeft = points[j].fX; + bounds.fRight = bounds.fLeft + + paint.measureText(&glyphs[j], sizeof(uint16_t), 0); + SkScalar baseline = points[j].fY; + bounds.fTop = baseline + fontMetrics.fAscent; + bounds.fBottom = baseline + fontMetrics.fDescent; + /* Accumulate and then add the resulting rect to mMatches */ r.join(bounds); } - getTotalMatrix().mapRect(&r); - // Add the text to our picture. -#if RECORD_MATCHES - mRecordingCanvas->setMatrix(getTotalMatrix()); - // FIXME: Need to do more work to get xPos and constY in the proper - // coordinates. - //mRecordingCanvas->drawPosText(glyphs, countInBytes, positions, paint); -#endif + SkMatrix matrix = getTotalMatrix(); + matrix.mapRect(&r); + SkCanvas* canvas = getWorkingCanvas(); + int saveCount = canvas->save(); + canvas->concat(matrix); + canvas->drawPosText(glyphs, countInBytes, points, paint); + canvas->restoreToCount(saveCount); return r; } @@ -168,24 +184,28 @@ SkRect FindCanvas::addMatchPosH(int index, const SkPaint& paint, int count, const uint16_t* glyphs, const SkScalar position[], SkScalar constY) { SkRect r; - r.setEmpty(); + // We only care about the positions starting at the index of our match const SkScalar* xPos = &position[index]; - for (int j = 0; j < count; j++) { - SkRect bounds; - paint.getTextWidths(&glyphs[j], 1, NULL, &bounds); - bounds.offset(xPos[j], constY); - /* Accumulate and then add the resulting rect to mRegions */ - r.join(bounds); - } - correctSize(paint, r); - getTotalMatrix().mapRect(&r); - // Add the text to our picture. -#if RECORD_MATCHES - mRecordingCanvas->setMatrix(getTotalMatrix()); - // FIXME: Need to do more work to get xPos and constY in the proper - // coordinates. - //mRecordingCanvas->drawPosTextH(glyphs, count << 1, xPos, constY, paint); -#endif + // This assumes that the position array is monotonic increasing + // The left bounds will be the position of the left most character + r.fLeft = xPos[0]; + // The right bounds will be the position of the last character plus its + // width + int lastIndex = count - 1; + r.fRight = paint.measureText(&glyphs[lastIndex], sizeof(uint16_t), 0) + + xPos[lastIndex]; + // Grab font metrics to determine the top and bottom of the bounds + SkPaint::FontMetrics fontMetrics; + paint.getFontMetrics(&fontMetrics); + r.fTop = constY + fontMetrics.fAscent; + r.fBottom = constY + fontMetrics.fDescent; + const SkMatrix& matrix = getTotalMatrix(); + matrix.mapRect(&r); + SkCanvas* canvas = getWorkingCanvas(); + int saveCount = canvas->save(); + canvas->concat(matrix); + canvas->drawPosTextH(glyphs, count * sizeof(uint16_t), xPos, constY, paint); + canvas->restoreToCount(saveCount); return r; } @@ -236,10 +256,7 @@ void FindCanvas::findHelper(const void* text, size_t byteLength, const uint16_t* glyphs, const SkScalar positions[], SkScalar y)) { SkASSERT(paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding); - SkASSERT(mRegions); -#if RECORD_MATCHES - SkASSERT(mRecordingCanvas); -#endif + SkASSERT(mMatches); GlyphSet* glyphSet = getGlyphs(paint); const int count = glyphSet->getCount(); int numCharacters = byteLength >> 1; @@ -249,7 +266,8 @@ void FindCanvas::findHelper(const void* text, size_t byteLength, if (mWorkingIndex) { SkPoint newY; getTotalMatrix().mapXY(0, y, &newY); - if (mWorkingRegion.getBounds().fBottom < SkScalarRound(newY.fY)) { + SkIRect bounds = mWorkingRegion.getBounds(); + if (bounds.fBottom < SkScalarRound(newY.fY)) { // Now we know that this line is lower than our partial match. SkPaint clonePaint(paint); clonePaint.setTextEncoding(SkPaint::kUTF8_TextEncoding); @@ -260,14 +278,16 @@ void FindCanvas::findHelper(const void* text, size_t byteLength, if (mWorkingIndex == count) { // We already know that it is not clipped out because we // checked for that before saving the working region. - mNumFound++; - mRegions->append(mWorkingRegion); + insertMatchInfo(mWorkingRegion); + + resetWorkingCanvas(); mWorkingIndex = 0; mWorkingRegion.setEmpty(); // We have found a match, so continue on this line from // scratch. } } else { + resetWorkingCanvas(); mWorkingIndex = 0; mWorkingRegion.setEmpty(); } @@ -288,7 +308,6 @@ void FindCanvas::findHelper(const void* text, size_t byteLength, } // The last count characters match, so we found the entire // search string. - mNumFound++; int remaining = count - mWorkingIndex; int matchIndex = index - remaining + 1; // Set up a pointer to the matching text in 'chars'. @@ -325,7 +344,7 @@ void FindCanvas::findHelper(const void* text, size_t byteLength, // the previous line(s). regionToAdd.op(mWorkingRegion, SkRegion::kUnion_Op); } - mRegions->append(regionToAdd); + insertMatchInfo(regionToAdd); #if INCLUDE_SUBSTRING_MATCHES // Reset index to the location of the match and reset j to the // beginning, so that on the next iteration of the loop, index @@ -338,6 +357,9 @@ void FindCanvas::findHelper(const void* text, size_t byteLength, // character from our hidden match index = matchIndex; } + // Whether the clip contained it or not, we need to start over + // with our recording canvas + resetWorkingCanvas(); } else { // Index needs to be set to index - j + 1. // This is a ridiculous case, but imagine the situation where the @@ -389,6 +411,14 @@ void FindCanvas::findHelper(const void* text, size_t byteLength, mWorkingIndex = 0; } +SkCanvas* FindCanvas::getWorkingCanvas() { + if (!mWorkingPicture) { + mWorkingPicture = new SkPicture; + mWorkingCanvas = mWorkingPicture->beginRecording(0,0); + } + return mWorkingCanvas; +} + GlyphSet* FindCanvas::getGlyphs(const SkPaint& paint) { SkTypeface* typeface = paint.getTypeface(); GlyphSet* end = mGlyphSets.end(); @@ -402,3 +432,18 @@ GlyphSet* FindCanvas::getGlyphs(const SkPaint& paint) { *mGlyphSets.append() = set; return &(mGlyphSets.top()); } + +void FindCanvas::insertMatchInfo(const SkRegion& region) { + mNumFound++; + mWorkingPicture->endRecording(); + MatchInfo matchInfo; + mMatches->append(matchInfo); + mMatches->last().set(region, mWorkingPicture); +} + +void FindCanvas::resetWorkingCanvas() { + mWorkingPicture->unref(); + mWorkingPicture = 0; + // Do not need to reset mWorkingCanvas itself because we only access it via + // getWorkingCanvas. +} diff --git a/WebKit/android/nav/FindCanvas.h b/WebKit/android/nav/FindCanvas.h index fe1e3d2..5558c8b 100644 --- a/WebKit/android/nav/FindCanvas.h +++ b/WebKit/android/nav/FindCanvas.h @@ -17,23 +17,37 @@ #ifndef Find_Canvas_h #define Find_Canvas_h -// The code marked with this is used to record the draw calls into an SkPicture, -// which is passed to the caller to draw the matches on top of the opaque green -// rectangles. The code is a checkpoint. -#define RECORD_MATCHES 0 - #include "SkBounder.h" #include "SkCanvas.h" +#include "SkPicture.h" #include "SkRegion.h" #include "SkTDArray.h" #include "icu/unicode/umachine.h" -#if RECORD_MATCHES -class SkPicture; -#endif class SkRect; class SkTypeface; +// Stores both region information and an SkPicture of the match, so that the +// region can be drawn, followed by drawing the matching text on top of it. +// This class owns its SkPicture +class MatchInfo { +public: + MatchInfo(); + ~MatchInfo(); + MatchInfo(const MatchInfo& src); + const SkRegion& getLocation() const { return m_location; } + // Return a pointer to our picture, representing the matching text. Does + // not transfer ownership of the picture. + SkPicture* getPicture() const { return m_picture; } + // This will make a copy of the region, and increase the ref count on the + // SkPicture. If this MatchInfo already had one, unref it. + void set(const SkRegion& region, SkPicture* pic); +private: + MatchInfo& operator=(MatchInfo& src); + SkRegion m_location; + SkPicture* m_picture; +}; + // A class containing a typeface for reference, the length in glyphs, and // the upper and lower case representations of the search string. class GlyphSet { @@ -113,20 +127,14 @@ public: int found() const { return mNumFound; } - // This method detaches our array of regions and passes ownership to + // This method detaches our array of matches and passes ownership to // the caller, who is then responsible for deleting them. - WTF::Vector<SkRegion>* detachRegions() { - WTF::Vector<SkRegion>* array = mRegions; - mRegions = NULL; + WTF::Vector<MatchInfo>* detachMatches() { + WTF::Vector<MatchInfo>* array = mMatches; + mMatches = NULL; return array; } -#if RECORD_MATCHES - // This SkPicture contains only draw calls for the drawn text. This is - // used to draw over the highlight rectangle so that it can be seen. - SkPicture* getDrawnMatches() const { return mPicture; } -#endif - private: // These calls are made by findHelper to store information about each match // that is found. They return a rectangle which is used to highlight the @@ -144,7 +152,7 @@ private: SkRect addMatchPosH(int index, const SkPaint& paint, int count, const uint16_t* glyphs, const SkScalar position[], SkScalar constY); - + // Helper for each of our draw calls void findHelper(const void* text, size_t byteLength, const SkPaint& paint, const SkScalar xPos[], SkScalar y, @@ -152,14 +160,25 @@ private: const SkPaint& paint, int count, const uint16_t* glyphs, const SkScalar pos[], SkScalar y)); + // If we already have a working canvas, grab it. Otherwise, create a new + // one. + SkCanvas* getWorkingCanvas(); + // Return the set of glyphs and its count for the text being searched for // and the parameter paint. If one has already been created and cached // for this paint, use it. If not, create a new one and cache it. GlyphSet* getGlyphs(const SkPaint& paint); + // Store all the accumulated info about a match in our vector. + void insertMatchInfo(const SkRegion& region); + + // Throw away our cumulative information about our working SkCanvas. After + // this call, next call to getWorkingCanvas will create a new one. + void resetWorkingCanvas(); + // Since we may transfer ownership of this array (see detachRects()), we // hold a pointer to the array instead of just the array itself. - WTF::Vector<SkRegion>* mRegions; + WTF::Vector<MatchInfo>* mMatches; const UChar* mLowerText; const UChar* mUpperText; size_t mLength; @@ -167,11 +186,9 @@ private: int mNumFound; SkScalar mOutset; SkTDArray<GlyphSet> mGlyphSets; -#if RECORD_MATCHES - SkPicture* mPicture; - SkCanvas* mRecordingCanvas; -#endif + SkPicture* mWorkingPicture; + SkCanvas* mWorkingCanvas; SkRegion mWorkingRegion; int mWorkingIndex; }; diff --git a/WebKit/android/nav/WebView.cpp b/WebKit/android/nav/WebView.cpp index 7af96d7..63c6aeb 100644 --- a/WebKit/android/nav/WebView.cpp +++ b/WebKit/android/nav/WebView.cpp @@ -419,10 +419,6 @@ WebView(JNIEnv* env, jobject javaWebView, int viewImpl) m_matches = 0; m_hasCurrentLocation = false; m_isFindPaintSetUp = false; -// RECORD_MATCHES is defined in FindCanvas.h -#if RECORD_MATCHES - m_matchesPicture = 0; -#endif } ~WebView() @@ -437,10 +433,6 @@ WebView(JNIEnv* env, jobject javaWebView, int viewImpl) delete m_navPictureUI; if (m_matches) delete m_matches; -// RECORD_MATCHES is defined in FindCanvas.h -#if RECORD_MATCHES - m_matchesPicture->safeUnref(); -#endif } void clearFocus(int x, int y, bool inval) @@ -548,9 +540,7 @@ void setUpFindPaint() const SkScalar roundiness = SkIntToScalar(2); SkCornerPathEffect* cornerEffect = new SkCornerPathEffect(roundiness); m_findPaint.setPathEffect(cornerEffect); - // FIXME: Would like this to be opaque, but then the user cannot see the - // text behind it. We will then need to redraw the text on top of it. - m_findPaint.setARGB(204, 132, 190, 0); + m_findPaint.setARGB(255, 132, 190, 0); // Set up the background blur paint. m_findBlurPaint.setAntiAlias(true); @@ -583,26 +573,34 @@ void drawMatch(const SkRegion& region, SkCanvas* canvas, bool focused) // Offset the path for a blurred shadow SkPath blurPath; matchPath.offset(SK_Scalar1, SkIntToScalar(2), &blurPath); + int saveCount = 0; + if (!focused) { + saveCount = canvas->save(); + canvas->clipPath(matchPath, SkRegion::kDifference_Op); + } // Draw the blurred background canvas->drawPath(blurPath, m_findBlurPaint); + if (!focused) { + canvas->restoreToCount(saveCount); + } // Draw the foreground canvas->drawPath(matchPath, m_findPaint); } +// Put a cap on the number of matches to draw. If the current page has more +// matches than this, only draw the focused match. +#define MAX_NUMBER_OF_MATCHES_TO_DRAW 101 + void drawMatches(SkCanvas* canvas) { - if (!m_matches || !m_matches->size() -// RECORD_MATCHES is defined in FindCanvas.h -#if RECORD_MATCHES - || !m_matchesPicture -#endif - ) { + if (!m_matches || !m_matches->size()) { return; } if (m_findIndex >= m_matches->size()) { m_findIndex = 0; } - const SkRegion& currentMatchRegion = (*m_matches)[m_findIndex]; + const MatchInfo& matchInfo = (*m_matches)[m_findIndex]; + const SkRegion& currentMatchRegion = matchInfo.getLocation(); const SkIRect& currentMatchBounds = currentMatchRegion.getBounds(); int left = currentMatchBounds.fLeft; int top = currentMatchBounds.fTop; @@ -636,36 +634,29 @@ void drawMatches(SkCanvas* canvas) setUpFindPaint(); // Draw the current match - drawMatch(currentMatchRegion, canvas, true); + drawMatch(currentMatchRegion, canvas, true); + // Now draw the picture, so that it shows up on top of the rectangle + canvas->drawPicture(*matchInfo.getPicture()); // Draw the rest unsigned numberOfMatches = m_matches->size(); - int saveCount = 0; - if (numberOfMatches > 1) { + if (numberOfMatches > 1 + && numberOfMatches < MAX_NUMBER_OF_MATCHES_TO_DRAW) { + SkIRect visibleIRect; + android_setrect(&visibleIRect, visible); for(unsigned i = 0; i < numberOfMatches; i++) { // The current match has already been drawn if (i == m_findIndex) continue; - const SkRegion& region = (*m_matches)[i]; - // Do not draw matches which intersect the current one - if (currentMatchRegion.intersects(region)) + const SkRegion& region = (*m_matches)[i].getLocation(); + // Do not draw matches which intersect the current one, or if it is + // offscreen + if (currentMatchRegion.intersects(region) + || !region.intersects(visibleIRect)) continue; drawMatch(region, canvas, false); } -// RECORD_MATCHES is defined in FindCanvas.h -#if RECORD_MATCHES - // Set a clip so we do not draw the text for the other matches. - saveCount = canvas->save(SkCanvas::kClip_SaveFlag); - canvas->clipRect(currentMatch); -#endif } -// RECORD_MATCHES is defined in FindCanvas.h -#if RECORD_MATCHES - canvas->drawPicture(*m_matchesPicture); - if (numberOfMatches > 1) { - canvas->restoreToCount(saveCount); - } -#endif } void drawFocusRing(SkCanvas* canvas) @@ -1112,6 +1103,18 @@ bool moveFocus(int keyCode, int count, bool ignoreScroll, bool inval, void notifyFocusSet(FrameCachePermission inEditingMode) { + CachedRoot* root = getFrameCache(DontAllowNewer); + if (root) { + // make sure the mFocusData in WebView.java is in sync with WebView.cpp + const CachedFrame* frame = 0; + const CachedNode* node = root->currentFocus(&frame); + const WebCore::IntPoint& focusLocation = root->focusLocation(); + setFocusData(root->generation(), + frame ? (WebCore::Frame*) frame->framePointer() : 0, + node ? (WebCore::Node*) node->nodePointer() : 0, + focusLocation.x(), focusLocation.y(), false); + } + if (focusIsTextArea(inEditingMode)) updateTextEntry(); else if (inEditingMode) @@ -1608,8 +1611,8 @@ void setFocusData(int buildGeneration, WebCore::Frame* framePtr, // m_currentMatchLocation. void inline storeCurrentMatchLocation() { - SkASSERT(m_findIndex < m_matches->size() && m_findIndex >= 0); - const SkIRect& bounds = (*m_matches)[m_findIndex].getBounds(); + SkASSERT(m_findIndex < m_matches->size()); + const SkIRect& bounds = (*m_matches)[m_findIndex].getLocation().getBounds(); m_currentMatchLocation.set(bounds.fLeft, bounds.fTop); m_hasCurrentLocation = true; } @@ -1635,12 +1638,7 @@ void findNext(bool forward) // With this call, WebView takes ownership of matches, and is responsible for // deleting it. -void setMatches(WTF::Vector<SkRegion>* matches -// RECORD_MATCHES is defined in FindCanvas.h -#if RECORD_MATCHES - , SkPicture* pic -#endif - ) +void setMatches(WTF::Vector<MatchInfo>* matches) { if (m_matches) delete m_matches; @@ -1648,7 +1646,7 @@ void setMatches(WTF::Vector<SkRegion>* matches if (m_matches->size()) { if (m_hasCurrentLocation) { for (unsigned i = 0; i < m_matches->size(); i++) { - const SkIRect& rect = (*m_matches)[i].getBounds(); + const SkIRect& rect = (*m_matches)[i].getLocation().getBounds(); if (rect.fLeft == m_currentMatchLocation.fX && rect.fTop == m_currentMatchLocation.fY) { m_findIndex = i; @@ -1664,12 +1662,6 @@ void setMatches(WTF::Vector<SkRegion>* matches } else { m_hasCurrentLocation = false; } -// RECORD_MATCHES is defined in FindCanvas.h -#if RECORD_MATCHES - m_matchesPicture->safeUnref(); - m_matchesPicture = pic; - m_matchesPicture->ref(); -#endif viewInvalidate(); } @@ -1778,7 +1770,7 @@ private: // local state for WebView bool m_heightCanMeasure; int m_lastDx; SkMSec m_lastDxTime; - WTF::Vector<SkRegion>* m_matches; + WTF::Vector<MatchInfo>* m_matches; // Stores the location of the current match. SkIPoint m_currentMatchLocation; // Tells whether the value in m_currentMatchLocation is valid. @@ -1789,10 +1781,6 @@ private: // local state for WebView SkPaint m_findPaint; // Paint used for the background of our Find matches. SkPaint m_findBlurPaint; -// RECORD_MATCHES is defined in FindCanvas.h -#if RECORD_MATCHES - SkPicture* m_matchesPicture; -#endif unsigned m_findIndex; }; // end of WebView class @@ -2096,14 +2084,9 @@ static int nativeFindAll(JNIEnv *env, jobject obj, jstring findLower, bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); canvas.setBitmapDevice(bitmap); canvas.drawPicture(*(root->getPicture())); - WTF::Vector<SkRegion>* matches = canvas.detachRegions(); + WTF::Vector<MatchInfo>* matches = canvas.detachMatches(); // With setMatches, the WebView takes ownership of matches - view->setMatches(matches -// RECORD_MATCHES is defined in FindCanvas.h -#if RECORD_MATCHES - , canvas.getDrawnMatches() -#endif - ); + view->setMatches(matches); env->ReleaseStringChars(findLower, findLowerChars); env->ReleaseStringChars(findUpper, findUpperChars); diff --git a/WebKit/android/plugins/sample/main.cpp b/WebKit/android/plugins/sample/main.cpp index 5d2b0b8..5bb8ea0 100644 --- a/WebKit/android/plugins/sample/main.cpp +++ b/WebKit/android/plugins/sample/main.cpp @@ -349,6 +349,7 @@ int16 NPP_HandleEvent(NPP instance, void* event) evt->data.key.modifiers); if (evt->data.key.action == kDown_ANPKeyAction) { obj->mUnichar = evt->data.key.unichar; + browser->invalidaterect(instance, NULL); } return 1; diff --git a/WebKit/android/wds/Command.cpp b/WebKit/android/wds/Command.cpp new file mode 100644 index 0000000..132b8e5 --- /dev/null +++ b/WebKit/android/wds/Command.cpp @@ -0,0 +1,145 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "wds" +#include "config.h" + +#include "AndroidLog.h" +#include "CString.h" +#include "Command.h" +#include "Connection.h" +#include "DebugServer.h" +#include "Frame.h" +#include "RenderTreeAsText.h" +#include "RenderView.h" +#include "WebViewCore.h" +#include <utils/Log.h> + +#if ENABLE(WDS) + +using namespace WebCore; + +namespace android { + +namespace WDS { + +//------------------------------------------------------------------------------ +// Actual commands -- XXX should be moved somewhere else +//------------------------------------------------------------------------------ +static bool callDumpRenderTree(const Frame* frame, const Connection* conn) { + CString str = externalRepresentation(frame->contentRenderer()).latin1(); + conn->write(str.data(), str.length()); + return true; +} + +static bool callDumpDomTree(const Frame* frame, const Connection* conn) { + WebViewCore::getWebViewCore(frame->view())->dumpDomTree(true); + + FILE* f = fopen(DOM_TREE_LOG_FILE, "r"); + if (!f) { + conn->write("Dom tree written to logcat\n"); + } else { + char buf[512]; + while (true) { + int nread = fread(buf, 1, sizeof(buf), f); + if (nread <= 0) + break; + conn->write(buf, nread); + } + fclose(f); + } + return true; +} + +class WebCoreHandler : public Handler { +public: + virtual void post(TargetThreadFunction func, void* v) const { + callOnMainThread(func, v); + } +}; +static WebCoreHandler s_webcoreHandler; + +//------------------------------------------------------------------------------ +// End command section +//------------------------------------------------------------------------------ + +class InternalCommand : public Command { +public: + InternalCommand(const Command* comm, const Frame* frame, + const Connection* connection) + : Command(*comm) + , m_frame(frame) + , m_connection(connection) {} + virtual ~InternalCommand() { delete m_connection; } + + void doCommand() const { + LOGD("Executing command '%s' (%s)", m_name, m_description); + if (!m_dispatch(m_frame, m_connection)) + // XXX: Have useful failure messages + m_connection->write("EPIC FAIL!\n", 11); + } + +private: + const Frame* m_frame; + const Connection* m_connection; +}; + +static void commandDispatcher(void* v) { + InternalCommand* c = static_cast<InternalCommand*>(v); + c->doCommand(); + delete c; +} + +void Command::dispatch() { + m_handler.post(commandDispatcher, this); +} + +Vector<const Command*>* Command::s_commands; + +void Command::Init() { + // Do not initialize twice. + if (s_commands) + return; + // XXX: Move this somewhere else. + s_commands = new Vector<const Command*>(); + s_commands->append(new Command("DDOM", "Dump Dom Tree", + callDumpDomTree, s_webcoreHandler)); + s_commands->append(new Command("DDRT", "Dump Render Tree", + callDumpRenderTree, s_webcoreHandler)); +} + +Command* Command::Find(const Connection* conn) { + char buf[COMMAND_LENGTH]; + if (conn->read(buf, sizeof(buf)) != COMMAND_LENGTH) + return NULL; + + // Linear search of commands. TODO: binary search when more commands are + // added. + Vector<const Command*>::const_iterator i = s_commands->begin(); + Vector<const Command*>::const_iterator end = s_commands->end(); + while (i != end) { + if (strncmp(buf, (*i)->name(), sizeof(buf)) == 0) + return new InternalCommand(*i, server()->getFrame(0), conn); + i++; + } + return NULL; +} + +} // end namespace WDS + +} // end namespace android + +#endif diff --git a/WebKit/android/wds/Command.h b/WebKit/android/wds/Command.h new file mode 100644 index 0000000..df45b2b --- /dev/null +++ b/WebKit/android/wds/Command.h @@ -0,0 +1,99 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WDS_COMMAND_H +#define WDS_COMMAND_H + +#include "wtf/MainThread.h" +#include "wtf/Vector.h" + +namespace WebCore { +class Frame; +} + +using namespace WTF; +using namespace WebCore; + +namespace android { + +// WebCore Debug Server +namespace WDS { + +class Connection; + +// Command identifier length +#define COMMAND_LENGTH 4 + +// The dispatcher function called with a Frame for context and the established +// connection to the client. The connection can be used to read and write to the +// client application. Return true on successful completion of the command, +// return false to indicate failure. +typedef bool (*DispatchFunction)(const Frame*, const Connection*); + +// Note: Although the type is named MainThreadFunction, it may not always be +// the main thread. The type is generic enough to reuse here but named +// something more appropriate. +typedef MainThreadFunction TargetThreadFunction; + +// Helper class to dipatch functions on a particular thread. +class Handler { +public: + virtual ~Handler() {} + virtual void post(TargetThreadFunction, void*) const = 0; +}; + +// Class for containing information about particular commands. +class Command { +public: + Command(const char* name, const char* desc, const DispatchFunction func, + const Handler& handler) + : m_name(name) + , m_description(desc) + , m_dispatch(func) + , m_handler(handler) {} + Command(const Command& comm) + : m_name(comm.m_name) + , m_description(comm.m_description) + , m_dispatch(comm.m_dispatch) + , m_handler(comm.m_handler) {} + virtual ~Command() {} + + // Initialize the debug server commands + static void Init(); + + // Find the command specified by the client request. + static Command* Find(const Connection* conn); + + // Dispatch this command + void dispatch(); + + const char* name() const { return m_name; } + +protected: + const char* m_name; + const char* m_description; + const DispatchFunction m_dispatch; + +private: + const Handler& m_handler; + static Vector<const Command*>* s_commands; +}; + +} // end namespace WDS + +} // end namespace android + +#endif diff --git a/WebKit/android/wds/Connection.cpp b/WebKit/android/wds/Connection.cpp new file mode 100644 index 0000000..b6ebd75 --- /dev/null +++ b/WebKit/android/wds/Connection.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "wds" +#include "config.h" + +#include "DebugServer.h" // used for ENABLE_WDS +#include "Connection.h" +#include <arpa/inet.h> +#include <string.h> +#include <utils/Log.h> + +#if ENABLE(WDS) + +#define MAX_CONNECTION_QUEUE 5 +#define log_errno(x) LOGE("%s: %d", x, strerror(errno)) + +namespace android { + +namespace WDS { + +bool Socket::open() { + m_fd = socket(PF_INET, SOCK_STREAM, 0); + if (m_fd < 0) { + log_errno("Failed to create file descriptor"); + return false; + } + return true; +} + +bool ConnectionServer::connect(short port) { + if (!m_socket.open()) + return false; + int fd = m_socket.fd(); + + // Build our sockaddr_in structure use to listen to incoming connections + sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + + // Try to bind to the given port + if (bind(fd, (sockaddr*) &addr, sizeof(addr)) < 0) { + log_errno("Failed to bind to local host"); + return false; + } + + // Try to listen + if (listen(fd, MAX_CONNECTION_QUEUE) < 0) { + log_errno("Failed to listen"); + return false; + } + + return true; +} + +Connection* ConnectionServer::accept() const { + int conn = ::accept(m_socket.fd(), NULL, NULL); + if (conn < 0) { + log_errno("Accept failed"); + return NULL; + } + return new Connection(conn); +} + +} // end namespace WDS + +} // end namespace android + +#endif diff --git a/WebKit/android/wds/Connection.h b/WebKit/android/wds/Connection.h new file mode 100644 index 0000000..f7fa21b --- /dev/null +++ b/WebKit/android/wds/Connection.h @@ -0,0 +1,76 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WDS_CONNECTION_H +#define WDS_CONNECTION_H + +#include <sys/socket.h> + +namespace android { + +namespace WDS { + +class Socket { +public: + Socket(): m_fd(-1) {} + Socket(int fd): m_fd(fd) {} + ~Socket() { + if (m_fd != -1) { + shutdown(m_fd, SHUT_RDWR); + close(m_fd); + } + } + // Open a new socket using PF_INET and SOCK_STREAM + bool open(); + int fd() const { return m_fd; } +private: + int m_fd; +}; + +class Connection { +public: + Connection(int conn): m_socket(conn) {} + int read(char buf[], size_t length) const { + return recv(m_socket.fd(), buf, length, 0); + } + int write(const char buf[], size_t length) const { + return send(m_socket.fd(), buf, length, 0); + } + int write(const char buf[]) const { + return write(buf, strlen(buf)); + } +private: + Socket m_socket; +}; + +class ConnectionServer { +public: + ConnectionServer() {} + + // Establish a connection to the local host on the given port. + bool connect(short port); + + // Blocks on the established socket until a new connection arrives. + Connection* accept() const; +private: + Socket m_socket; +}; + +} // end namespace WDS + +} // end namespace android + +#endif diff --git a/WebKit/android/wds/DebugServer.cpp b/WebKit/android/wds/DebugServer.cpp new file mode 100644 index 0000000..8581680 --- /dev/null +++ b/WebKit/android/wds/DebugServer.cpp @@ -0,0 +1,107 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "wds" +#include "config.h" + +#include "Command.h" +#include "Connection.h" +#include "DebugServer.h" +#include "wtf/MainThread.h" +#include "wtf/Threading.h" +#include <arpa/inet.h> +#include <cutils/properties.h> +#include <errno.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <utils/Log.h> + +#if ENABLE(WDS) + +#define DEFAULT_PORT 9999 +#define log_errno(x) LOGE("%s: %d", x, strerror(errno)) + +namespace android { + +namespace WDS { + +static DebugServer* s_server = NULL; + +// Main thread function for createThread +static void* mainThread(void* v) { + DebugServer* server = static_cast<DebugServer*>(v); + server->start(); + delete server; + s_server = NULL; + return NULL; +} + +DebugServer* server() { + if (s_server == NULL) + s_server = new DebugServer(); + return s_server; +} + +DebugServer::DebugServer() { + // Read webcore.wds.enable to determine if the debug server should run + char buf[PROPERTY_VALUE_MAX]; + int ret = property_get("webcore.wds.enable", buf, NULL); + if (ret != -1 && strcmp(buf, "1") == 0) { + LOGD("WDS Enabled"); + m_threadId = createThread(mainThread, this, "WDS"); + } + // Initialize the available commands. + Command::Init(); +} + +void DebugServer::start() { + LOGD("DebugServer thread started"); + + ConnectionServer cs; + if (!cs.connect(DEFAULT_PORT)) { + LOGE("Failed to start the server socket connection"); + return; + } + + while (true ) { + LOGD("Waiting for incoming connections..."); + Connection* conn = cs.accept(); + if (!conn) { + log_errno("Failed to accept new connections"); + return; + } + LOGD("...Connection established"); + + Command* c = Command::Find(conn); + if (!c) { + LOGE("Could not find matching command"); + delete conn; + } else { + // Dispatch the command, it will handle cleaning up the connection + // when finished. + c->dispatch(); + } + } + + LOGD("DebugServer thread finished"); +} + +} // end namespace WDS + +} // end namespace android + +#endif diff --git a/WebKit/android/wds/DebugServer.h b/WebKit/android/wds/DebugServer.h new file mode 100644 index 0000000..64db031 --- /dev/null +++ b/WebKit/android/wds/DebugServer.h @@ -0,0 +1,68 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DEBUGSERVER_H +#define DEBUGSERVER_H + +// Turn on the wds feature in webkit +#define ENABLE_WDS 0 + +#include "wtf/Threading.h" +#include "wtf/Vector.h" + +// Forward declarations. +namespace WebCore { + class Frame; +} + +using namespace WTF; +using namespace WebCore; + +namespace android { + +// WebCore Debug Server +namespace WDS { + +class DebugServer : WTFNoncopyable::Noncopyable { +public: + void start(); + void addFrame(Frame* frame) { + m_frames.append(frame); + } + void removeFrame(Frame* frame) { + size_t i = m_frames.find(frame); + if (i != notFound) + m_frames.remove(i); + } + Frame* getFrame(unsigned idx) { + if (idx < m_frames.size()) + return m_frames.at(idx); + return NULL; + } +private: + DebugServer(); + Vector<Frame*> m_frames; + ThreadIdentifier m_threadId; + friend DebugServer* server(); +}; + +DebugServer* server(); + +} // end namespace WDS + +} // end namespace android + +#endif diff --git a/WebKit/android/wds/client/AdbConnection.cpp b/WebKit/android/wds/client/AdbConnection.cpp new file mode 100644 index 0000000..79b49d7 --- /dev/null +++ b/WebKit/android/wds/client/AdbConnection.cpp @@ -0,0 +1,228 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "wdsclient" + +#include "AdbConnection.h" +#include "ClientUtils.h" +#include "Device.h" +#include <arpa/inet.h> +#include <errno.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <utils/Log.h> + +void AdbConnection::close() { + if (m_fd != -1) { + shutdown(m_fd, SHUT_RDWR); + ::close(m_fd); + m_fd = -1; + } +} + +// Default adb port +#define ADB_PORT 5037 + +bool AdbConnection::connect() { + // Some commands (host:devices for example) close the connection so we call + // connect after the response. + close(); + + m_fd = socket(PF_INET, SOCK_STREAM, 0); + if (m_fd < 0) { + log_errno("Failed to create socket for connecting to adb"); + return false; + } + + // Create the socket address struct + sockaddr_in adb; + createTcpSocket(adb, ADB_PORT); + + // Connect to adb + if (::connect(m_fd, (sockaddr*) &adb, sizeof(adb)) < 0) { + log_errno("Failed to connect to adb"); + return false; + } + + // Connected + return true; +} + +// Adb protocol stuff +#define MAX_COMMAND_LENGTH 1024 +#define PAYLOAD_LENGTH 4 +#define PAYLOAD_FORMAT "%04X" + +bool AdbConnection::sendRequest(const char* fmt, ...) const { + if (m_fd == -1) { + LOGE("Connection is closed"); + return false; + } + + // Build the command (service) + char buf[MAX_COMMAND_LENGTH]; + va_list args; + va_start(args, fmt); + int res = vsnprintf(buf, MAX_COMMAND_LENGTH, fmt, args); + va_end(args); + + LOGV("Sending command: %04X%.*s", res, res, buf); + + // Construct the payload length + char payloadLen[PAYLOAD_LENGTH + 1]; + snprintf(payloadLen, sizeof(payloadLen), PAYLOAD_FORMAT, res); + + // First, send the payload length + if (send(m_fd, payloadLen, PAYLOAD_LENGTH, 0) < 0) { + log_errno("Failure when sending payload"); + return false; + } + + // Send the actual command + if (send(m_fd, buf, res, 0) < 0) { + log_errno("Failure when sending command"); + return false; + } + + // Check for the OKAY from adb + return checkOkayResponse(); +} + +static void printFailureMessage(int fd) { + // Grab the payload length + char lenStr[PAYLOAD_LENGTH + 1]; + int payloadLen = recv(fd, lenStr, sizeof(lenStr) - 1, 0); + LOG_ASSERT(payloadLen == PAYLOAD_LENGTH, "Incorrect payload size"); + lenStr[PAYLOAD_LENGTH] = 0; + + // Parse the hex payload + payloadLen = strtol(lenStr, NULL, 16); + if (payloadLen < 0) + return; + + // Grab the message + char* msg = new char[payloadLen + 1]; // include null-terminator + int res = recv(fd, msg, payloadLen, 0); + if (res < 0) { + log_errno("Failure reading failure message from adb"); + return; + } else if (res != payloadLen) { + LOGE("Incorrect payload length %d - expected %d", res, payloadLen); + return; + } + msg[res] = 0; + + // Tell somebody about it + LOGE("Received failure from adb: %s", msg); + + // Cleanup + delete[] msg; +} + +#define ADB_RESPONSE_LENGTH 4 + +bool AdbConnection::checkOkayResponse() const { + LOG_ASSERT(m_fd != -1, "Connection has been closed!"); + + char buf[ADB_RESPONSE_LENGTH]; + int res = recv(m_fd, buf, sizeof(buf), 0); + if (res < 0) { + log_errno("Failure reading response from adb"); + return false; + } + + // Check for a response other than OKAY/FAIL + if ((res == ADB_RESPONSE_LENGTH) && (strncmp(buf, "OKAY", res) == 0)) { + LOGV("Command OKAY"); + return true; + } else if (strncmp(buf, "FAIL", ADB_RESPONSE_LENGTH) == 0) { + // Something happened, print out the reason for failure + printFailureMessage(m_fd); + return false; + } + LOGE("Incorrect response from adb - '%.*s'", res, buf); + return false; +} + +void AdbConnection::clearDevices() { + for (unsigned i = 0; i < m_devices.size(); i++) + delete m_devices.editItemAt(i); + m_devices.clear(); +} + +const DeviceList& AdbConnection::getDeviceList() { + // Clear the current device list + clearDevices(); + + if (m_fd == -1) { + LOGE("Connection is closed"); + return m_devices; + } + + // Try to send the device list request + if (!sendRequest("host:devices")) { + LOGE("Failed to get device list from adb"); + return m_devices; + } + + // Get the payload length + char lenStr[PAYLOAD_LENGTH + 1]; + int res = recv(m_fd, lenStr, sizeof(lenStr) - 1, 0); + if (res < 0) { + log_errno("Failure to read payload size of device list"); + return m_devices; + } + lenStr[PAYLOAD_LENGTH] = 0; + + // Parse the hex payload + int payloadLen = strtol(lenStr, NULL, 16); + if (payloadLen < 0) + return m_devices; + + // Grab the list of devices. The format is as follows: + // <serialno><tab><state><newline> + char* msg = new char[payloadLen + 1]; + res = recv(m_fd, msg, payloadLen, 0); + if (res < 0) { + log_errno("Failure reading the device list"); + return m_devices; + } else if (res != payloadLen) { + LOGE("Incorrect payload length %d - expected %d", res, payloadLen); + return m_devices; + } + msg[res] = 0; + + char serial[32]; + char state[32]; + int numRead; + char* ptr = msg; + while (sscanf(ptr, "%31s\t%31s\n%n", serial, state, &numRead) > 1) { + Device::DeviceType t = Device::DEVICE; + static const char emulator[] = "emulator-"; + if (strncmp(serial, emulator, sizeof(emulator) - 1) == 0) + t = Device::EMULATOR; + LOGV("Adding device %s (%s)", serial, state); + m_devices.add(new Device(serial, t, this)); + + // Reset for the next line + ptr += numRead; + } + // Cleanup + delete[] msg; + + return m_devices; +} diff --git a/WebKit/android/wds/client/AdbConnection.h b/WebKit/android/wds/client/AdbConnection.h new file mode 100644 index 0000000..0b5419f --- /dev/null +++ b/WebKit/android/wds/client/AdbConnection.h @@ -0,0 +1,38 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WDS_ADB_CONNECTION_H +#define WDS_ADB_CONNECTION_H + +#include "DeviceList.h" + +class AdbConnection { +public: + AdbConnection() : m_fd(-1) {} + ~AdbConnection() { clearDevices(); } + void close(); + bool connect(); + bool sendRequest(const char* fmt, ...) const; + const DeviceList& getDeviceList(); + +private: + bool checkOkayResponse() const; + void clearDevices(); + DeviceList m_devices; + int m_fd; +}; + +#endif diff --git a/WebKit/android/wds/client/Android.mk b/WebKit/android/wds/client/Android.mk new file mode 100644 index 0000000..5d07a31 --- /dev/null +++ b/WebKit/android/wds/client/Android.mk @@ -0,0 +1,32 @@ +## +## +## Copyright 2008, The Android Open Source Project +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + AdbConnection.cpp \ + ClientUtils.cpp \ + Device.cpp \ + main.cpp + +LOCAL_STATIC_LIBRARIES := liblog libutils libcutils + +LOCAL_MODULE:= wdsclient + +include $(BUILD_HOST_EXECUTABLE) + diff --git a/WebKit/android/wds/client/ClientUtils.cpp b/WebKit/android/wds/client/ClientUtils.cpp new file mode 100644 index 0000000..067775e --- /dev/null +++ b/WebKit/android/wds/client/ClientUtils.cpp @@ -0,0 +1,26 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ClientUtils.h" +#include <arpa/inet.h> +#include <string.h> + +void createTcpSocket(sockaddr_in& addr, short port) { + memset(&addr, 0, sizeof(sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); +} diff --git a/WebKit/android/wds/client/ClientUtils.h b/WebKit/android/wds/client/ClientUtils.h new file mode 100644 index 0000000..dc8b23f --- /dev/null +++ b/WebKit/android/wds/client/ClientUtils.h @@ -0,0 +1,29 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WDS_CLIENT_UTILS_H +#define WDS_CLIENT_UTILS_H + +#include <arpa/inet.h> + +// Callers need to include Log.h and errno.h to use this macro +#define log_errno(str) LOGE("%s: %s", str, strerror(errno)) + +// Fill in the sockaddr_in structure for binding to the localhost on the given +// port +void createTcpSocket(sockaddr_in& addr, short port); + +#endif diff --git a/WebKit/android/wds/client/Device.cpp b/WebKit/android/wds/client/Device.cpp new file mode 100644 index 0000000..03b4507 --- /dev/null +++ b/WebKit/android/wds/client/Device.cpp @@ -0,0 +1,22 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AdbConnection.h" +#include "Device.h" + +bool Device::sendRequest(const char* req) const { + return m_connection->sendRequest("host-serial:%s:%s", m_name, req); +} diff --git a/WebKit/android/wds/client/Device.h b/WebKit/android/wds/client/Device.h new file mode 100644 index 0000000..c6d77f3 --- /dev/null +++ b/WebKit/android/wds/client/Device.h @@ -0,0 +1,53 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WDS_DEVICE_H +#define WDS_DEVICE_H + +#include <stdlib.h> + +class AdbConnection; + +class Device { +public: + // Type of device. + // TODO: Add simulator support + enum DeviceType { + NONE = -1, + EMULATOR, + DEVICE + }; + + // Takes ownership of name + Device(char* name, DeviceType type, const AdbConnection* conn) + : m_connection(conn) + , m_name(strdup(name)) + , m_type(type) {} + ~Device() { free(m_name); } + + const char* name() const { return m_name; } + DeviceType type() const { return m_type; } + + // Send a request to this device. + bool sendRequest(const char* req) const; + +private: + const AdbConnection* m_connection; + char* m_name; + DeviceType m_type; +}; + +#endif diff --git a/WebKit/android/wds/client/DeviceList.h b/WebKit/android/wds/client/DeviceList.h new file mode 100644 index 0000000..9139238 --- /dev/null +++ b/WebKit/android/wds/client/DeviceList.h @@ -0,0 +1,26 @@ +/* + * Copyright 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WDS_DEVICE_LIST_H +#define WDS_DEVICE_LIST_H + +#include <utils/Vector.h> + +class Device; + +typedef android::Vector<Device*> DeviceList; + +#endif diff --git a/WebKit/android/wds/client/main.cpp b/WebKit/android/wds/client/main.cpp new file mode 100644 index 0000000..5899b16 --- /dev/null +++ b/WebKit/android/wds/client/main.cpp @@ -0,0 +1,164 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "wdsclient" + +#include "AdbConnection.h" +#include "ClientUtils.h" +#include "Device.h" +#include <arpa/inet.h> +#include <errno.h> +#include <getopt.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <utils/Log.h> + +#define DEFAULT_WDS_PORT 9999 +#define STR(x) #x +#define XSTR(x) STR(x) +#define PORT_STR XSTR(DEFAULT_WDS_PORT) + +int wds_open() { + // Create the structure for connecting to the forwarded 9999 port + sockaddr_in addr; + createTcpSocket(addr, DEFAULT_WDS_PORT); + + // Create our socket + int fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) { + log_errno("Failed to create file descriptor"); + return -1; + } + // Connect to the remote wds server thread + if (connect(fd, (sockaddr*)&addr, sizeof(addr)) < 0) { + log_errno("Failed to connect to remote debug server"); + return -1; + } + return fd; +} + +// Clean up the file descriptor and connections +void wds_close(int fd) { + if (fd != -1) { + shutdown(fd, SHUT_RDWR); + close(fd); + } +} + +int main(int argc, char** argv) { + + Device::DeviceType type = Device::NONE; + + if (argc <= 1) { + LOGE("wdsclient takes at least 1 argument"); + return 1; + } else { + // Parse the options, look for -e or -d to choose a device. + while (true) { + int c = getopt(argc, argv, "ed"); + if (c == -1) + break; + switch (c) { + case 'e': + type = Device::EMULATOR; + break; + case 'd': + type = Device::DEVICE; + break; + default: + break; + } + } + if (optind == argc) { + LOGE("No command specified"); + return 1; + } + } + + // Do the initial connection. + AdbConnection conn; + conn.connect(); + + const DeviceList& devices = conn.getDeviceList(); + // host:devices closes the connection, reconnect + conn.connect(); + + // No device specified and more than one connected, bail + if (type == Device::NONE && devices.size() > 1) { + LOGE("More than one device/emulator, please specify with -e or -d"); + return 1; + } else if (devices.size() == 0) { + LOGE("No devices connected"); + return 1; + } + + // Find the correct device + const Device* device = NULL; + if (type == Device::NONE) + device = devices[0]; // grab the only one + else { + // Search for a matching device type + for (unsigned i = 0; i < devices.size(); i++) { + if (devices[i]->type() == type) { + device = devices[i]; + break; + } + } + } + + if (!device) { + LOGE("No device found!"); + return 1; + } + + // Forward tcp:9999 + if (!device->sendRequest("forward:tcp:" PORT_STR ";tcp:" PORT_STR)) { + LOGE("Failed to send forwarding request"); + return 1; + } + + LOGV("Connecting to localhost port " PORT_STR); + + const char* command = argv[optind]; + int commandLen = strlen(command); +#define WDS_COMMAND_LENGTH 4 + if (commandLen != WDS_COMMAND_LENGTH) { + LOGE("Commands must be 4 characters '%s'", command); + return 1; + } + + // Open the wds connection + int wdsFd = wds_open(); + if (wdsFd == -1) + return 1; + + // Send the command specified + send(wdsFd, command, WDS_COMMAND_LENGTH, 0); // commands are 4 bytes + + // Read and display the response + char response[256]; + int res = 0; + while ((res = recv(wdsFd, response, sizeof(response), 0)) > 0) + printf("%.*s", res, response); + printf("\n\n"); + + // Shutdown + wds_close(wdsFd); + + return 0; +} |