1 /*
   2  * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.webkit;
  27 
  28 import com.sun.glass.utils.NativeLibLoader;
  29 import com.sun.webkit.event.WCFocusEvent;
  30 import com.sun.webkit.event.WCInputMethodEvent;
  31 import com.sun.webkit.event.WCKeyEvent;
  32 import com.sun.webkit.event.WCMouseEvent;
  33 import com.sun.webkit.event.WCMouseWheelEvent;
  34 import com.sun.webkit.graphics.*;
  35 import com.sun.webkit.network.CookieManager;
  36 import static com.sun.webkit.network.URLs.newURL;
  37 import java.net.CookieHandler;
  38 import java.net.MalformedURLException;
  39 import java.net.URL;
  40 import java.nio.ByteBuffer;
  41 import java.nio.ByteOrder;
  42 import java.security.AccessControlContext;
  43 import java.security.AccessController;
  44 import java.security.PrivilegedAction;
  45 import java.util.ArrayList;
  46 import java.util.HashMap;
  47 import java.util.HashSet;
  48 import java.util.Iterator;
  49 import java.util.LinkedList;
  50 import java.util.List;
  51 import java.util.Map;
  52 import java.util.Queue;
  53 import java.util.Set;
  54 import java.util.concurrent.CountDownLatch;
  55 import java.util.concurrent.ExecutionException;
  56 import java.util.concurrent.FutureTask;
  57 import java.util.concurrent.atomic.AtomicReference;
  58 import java.util.concurrent.locks.ReentrantLock;
  59 import java.util.logging.Level;
  60 import java.util.logging.Logger;
  61 import netscape.javascript.JSException;
  62 import org.w3c.dom.Document;
  63 import org.w3c.dom.Element;
  64 
  65 /**
  66  * This class provides two-side interaction between native webkit core and
  67  * number of clients representing different subsystems of the WebPane component
  68  * such as
  69  * <ul>
  70  * <li>webpage rendering({@link WebPageClient})
  71  * <li>creating/disposing web frames ({@link WebFrameClient})
  72  * <li>creating new windows, alert dialogues ... ({@link UIClient})
  73  * <li>handling menus {@link MenuClient}
  74  * <li>supporting policy checking {@link PolicyClient}
  75  * </ul>
  76  */
  77 
  78 public final class WebPage {
  79     private final static Logger log = Logger.getLogger(WebPage.class.getName());
  80     private final static Logger paintLog = Logger.getLogger(WebPage.class.getName() + ".paint");
  81 
  82     private static final int MAX_FRAME_QUEUE_SIZE = 10;
  83 
  84     // Native WebPage* pointer
  85     private long pPage = 0;
  86 
  87     // A flag to distinguish whether the web page hasn't been created
  88     // yet or had been already disposed - in both cases pPage is 0
  89     private boolean isDisposed = false;
  90 
  91     private int width, height;
  92 
  93     private int fontSmoothingType;
  94 
  95     private final WCFrameView hostWindow;
  96 
  97     // List of created frames
  98     private final Set<Long> frames = new HashSet<Long>();
  99 
 100     // The access control context associated with this object
 101     private final AccessControlContext accessControlContext;
 102 
 103     // Maps load request identifiers to URLs
 104     private final Map<Integer, String> requestURLs =
 105             new HashMap<Integer, String>();
 106 
 107     // There may be several RESOURCE_STARTED events for a resource,
 108     // so this map is used to convert them to RESOURCE_REDIRECTED
 109     private final Set<Integer> requestStarted = new HashSet<Integer>();
 110 
 111     // PAGE_LOCK is used to synchronize the following operations b/w Event & Main threads:
 112     // - rendering of the page (Main thread)
 113     // - native calls & other manipulations on the page (Event & Main threads)
 114     // - timer invocations (Event thread)
 115     private static final ReentrantLock PAGE_LOCK = new ReentrantLock();
 116 
 117     // The queue of render frames awaiting rendering.
 118     // Access to this object is synchronized on its monitor.
 119     // Accessed on: Event thread and Main thread.
 120     private final Queue<RenderFrame> frameQueue = new LinkedList<RenderFrame>();
 121 
 122     // The current frame being generated.
 123     // Accessed on: Event thread only.
 124     private RenderFrame currentFrame = new RenderFrame();
 125 
 126     // An ID of the current updateContent cycle associated with an updateContent call.
 127     private int updateContentCycleID;
 128 
 129     static {
 130         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 131             NativeLibLoader.loadLibrary("jfxwebkit");
 132             log.finer("jfxwebkit loaded");
 133 
 134             if (CookieHandler.getDefault() == null) {
 135                 boolean setDefault = Boolean.valueOf(System.getProperty(
 136                         "com.sun.webkit.setDefaultCookieHandler",
 137                         "true"));
 138                 if (setDefault) {
 139                     CookieHandler.setDefault(new CookieManager());
 140                 }
 141             }
 142 
 143             final boolean useJIT = Boolean.valueOf(System.getProperty(
 144                     "com.sun.webkit.useJIT", "true"));
 145             final boolean useDFGJIT = Boolean.valueOf(System.getProperty(
 146                     "com.sun.webkit.useDFGJIT", "true"));
 147 
 148             // Initialize WTF, WebCore and JavaScriptCore.
 149             twkInitWebCore(useJIT, useDFGJIT);
 150             return null;
 151         });
 152 
 153     }
 154 
 155     private static boolean firstWebPageCreated = false;
 156 
 157     private static void collectJSCGarbages() {
 158         Invoker.getInvoker().checkEventThread();
 159         // Add dummy object to get notification as soon as it is collected
 160         // by the JVM GC.
 161         Disposer.addRecord(new Object(), WebPage::collectJSCGarbages);
 162         // Invoke JavaScriptCore GC.
 163         twkDoJSCGarbageCollection();
 164     }
 165 
 166     public WebPage(WebPageClient pageClient,
 167                    UIClient uiClient,
 168                    PolicyClient policyClient,
 169                    InspectorClient inspectorClient,
 170                    ThemeClient themeClient,
 171                    boolean editable)
 172     {
 173         Invoker.getInvoker().checkEventThread();
 174 
 175         this.pageClient = pageClient;
 176         this.uiClient = uiClient;
 177         this.policyClient = policyClient;
 178         this.inspectorClient = inspectorClient;
 179         if (themeClient != null) {
 180             this.renderTheme = themeClient.createRenderTheme();
 181             this.scrollbarTheme = themeClient.createScrollBarTheme();
 182         } else {
 183             this.renderTheme = null;
 184             this.scrollbarTheme = null;
 185         }
 186 
 187         accessControlContext = AccessController.getContext();
 188 
 189         hostWindow = new WCFrameView(this);
 190         pPage = twkCreatePage(editable);
 191 
 192         twkInit(pPage, false, WCGraphicsManager.getGraphicsManager().getDevicePixelScale());
 193 
 194         if (pageClient != null && pageClient.isBackBufferSupported()) {
 195             backbuffer = pageClient.createBackBuffer();
 196             backbuffer.ref();
 197         }
 198 
 199         if (!firstWebPageCreated) {
 200             // Add dummy object to get notification as soon as it is collected
 201             // by the JVM GC.
 202             Disposer.addRecord(new Object(), WebPage::collectJSCGarbages);
 203             firstWebPageCreated = true;
 204         }
 205     }
 206 
 207     long getPage() {
 208         return pPage;
 209     }
 210 
 211     // Called from the native code
 212     private WCWidget getHostWindow() {
 213         return hostWindow;
 214     }
 215 
 216     /**
 217      * Returns the access control context associated with this object.
 218      * May be called on any thread.
 219      * @return the access control context associated with this object
 220      */
 221     public AccessControlContext getAccessControlContext() {
 222         return accessControlContext;
 223     }
 224 
 225     static boolean lockPage() {
 226         return Invoker.getInvoker().lock(PAGE_LOCK);
 227     }
 228 
 229     static boolean unlockPage() {
 230         return Invoker.getInvoker().unlock(PAGE_LOCK);
 231     }
 232 
 233     // *************************************************************************
 234     // Backbuffer support
 235     // *************************************************************************
 236 
 237     private WCPageBackBuffer backbuffer;
 238     private List<WCRectangle> dirtyRects = new LinkedList<WCRectangle>();
 239 
 240     private void addDirtyRect(WCRectangle toPaint) {
 241         if (toPaint.getWidth() <= 0 || toPaint.getHeight() <= 0) {
 242             return;
 243         }
 244         for (Iterator<WCRectangle> it = dirtyRects.iterator(); it.hasNext();) {
 245             WCRectangle rect = it.next();
 246             // if already covered
 247             if (rect.contains(toPaint)) {
 248                 return;
 249             }
 250             // if covers an existing one
 251             if (toPaint.contains(rect)) {
 252                 it.remove();
 253                 continue;
 254             }
 255             WCRectangle u = rect.createUnion(toPaint);
 256             // if squre of union is less than summary of squares
 257             if (u.getIntWidth() * u.getIntHeight() <
 258                 rect.getIntWidth() * rect.getIntHeight() +
 259                 toPaint.getIntWidth() * toPaint.getIntHeight())
 260             {
 261                 it.remove();
 262                 toPaint = u; // replace both the rects with their union
 263                 continue;
 264             }
 265         }
 266         dirtyRects.add(toPaint);
 267     }
 268 
 269     public boolean isDirty() {
 270         lockPage();
 271         try {
 272             return !dirtyRects.isEmpty();
 273         } finally {
 274             unlockPage();
 275         }
 276     }
 277 
 278     private void updateDirty(WCRectangle clip) {
 279         if (paintLog.isLoggable(Level.FINEST)) {
 280             paintLog.log(Level.FINEST, "Entering, "
 281                     + "dirtyRects: {0}, currentFrame: {1}",
 282                     new Object[] {dirtyRects, currentFrame});
 283         }
 284 
 285         if (isDisposed || width <= 0 || height <= 0) {
 286             // If there're any dirty rects left, they are invalid.
 287             // Clear the list so that the platform doesn't consider
 288             // the page dirty.
 289             dirtyRects.clear();
 290             return;
 291         }
 292         if (clip == null) {
 293             clip = new WCRectangle(0, 0, width, height);
 294         }
 295         List<WCRectangle> oldDirtyRects = dirtyRects;
 296         dirtyRects = new LinkedList<WCRectangle>();
 297         twkPrePaint(getPage());
 298         while (!oldDirtyRects.isEmpty()) {
 299             WCRectangle r = oldDirtyRects.remove(0).intersection(clip);
 300             if (r.getWidth() <= 0 || r.getHeight() <= 0) {
 301                 continue;
 302             }
 303             paintLog.log(Level.FINEST, "Updating: {0}", r);
 304             WCRenderQueue rq = WCGraphicsManager.getGraphicsManager()
 305                     .createRenderQueue(r, true);
 306             twkUpdateContent(getPage(), rq, r.getIntX() - 1, r.getIntY() - 1,
 307                              r.getIntWidth() + 2, r.getIntHeight() + 2);
 308             currentFrame.addRenderQueue(rq);
 309         }
 310         {
 311             WCRenderQueue rq = WCGraphicsManager.getGraphicsManager()
 312                     .createRenderQueue(clip, false);
 313             twkPostPaint(getPage(), rq,
 314                          clip.getIntX(), clip.getIntY(),
 315                          clip.getIntWidth(), clip.getIntHeight());
 316             currentFrame.addRenderQueue(rq);
 317         }
 318 
 319         if (paintLog.isLoggable(Level.FINEST)) {
 320             paintLog.log(Level.FINEST, "Dirty rects processed, "
 321                     + "dirtyRects: {0}, currentFrame: {1}",
 322                     new Object[] {dirtyRects, currentFrame});
 323         }
 324 
 325         if (currentFrame.getRQList().size() > 0) {
 326             synchronized (frameQueue) {
 327                 paintLog.log(Level.FINEST, "About to update frame queue, "
 328                         + "frameQueue: {0}", frameQueue);
 329 
 330                 Iterator<RenderFrame> it = frameQueue.iterator();
 331                 while (it.hasNext()) {
 332                     RenderFrame frame = it.next();
 333                     for (WCRenderQueue rq : currentFrame.getRQList()) {
 334                         WCRectangle rqRect = rq.getClip();
 335                         if (rq.isOpaque()
 336                                 && rqRect.contains(frame.getEnclosingRect()))
 337                         {
 338                             paintLog.log(Level.FINEST, "Dropping: {0}", frame);
 339                             frame.drop();
 340                             it.remove();
 341                             break;
 342                         }
 343                     }
 344                 }
 345 
 346                 frameQueue.add(currentFrame);
 347                 currentFrame = new RenderFrame();
 348 
 349                 if (frameQueue.size() > MAX_FRAME_QUEUE_SIZE) {
 350                     paintLog.log(Level.FINEST, "Frame queue exceeded maximum "
 351                             + "size, clearing and requesting full repaint");
 352                     dropRenderFrames();
 353                     repaintAll();
 354                 }
 355 
 356                 paintLog.log(Level.FINEST, "Frame queue updated, "
 357                         + "frameQueue: {0}", frameQueue);
 358             }
 359         }
 360 
 361         if (paintLog.isLoggable(Level.FINEST)) {
 362             paintLog.log(Level.FINEST,
 363                     "Exiting, dirtyRects: {0}, currentFrame: {1}",
 364                     new Object[] {dirtyRects, currentFrame});
 365         }
 366     }
 367 
 368     private void scroll(int x, int y, int w, int h, int dx, int dy) {
 369         if (paintLog.isLoggable(Level.FINEST)) {
 370             paintLog.finest("rect=[" + x + ", " + y + " " + w + "x" + h +
 371                             "] delta=[" + dx + ", " + dy + "]");
 372         }
 373         dx += currentFrame.scrollDx;
 374         dy += currentFrame.scrollDy;
 375 
 376         if (Math.abs(dx) < w && Math.abs(dy) < h) {
 377             int cx = (dx >= 0) ? x : x - dx;
 378             int cy = (dy >= 0) ? y : y - dy;
 379             int cw = (dx == 0) ? w : w - Math.abs(dx);
 380             int ch = (dy == 0) ? h : h - Math.abs(dy);
 381 
 382             WCRenderQueue rq = WCGraphicsManager.getGraphicsManager()
 383                     .createRenderQueue(
 384                             new WCRectangle(0, 0, width, height), false);
 385             ByteBuffer buffer = ByteBuffer.allocate(32)
 386                     .order(ByteOrder.nativeOrder())
 387                     .putInt(GraphicsDecoder.COPYREGION)
 388                     .putInt(backbuffer.getID())
 389                     .putInt(cx).putInt(cy).putInt(cw).putInt(ch)
 390                     .putInt(dx).putInt(dy);
 391             buffer.flip();
 392             rq.addBuffer(buffer);
 393             // Ignore previous COPYREGION
 394             currentFrame.drop();
 395             currentFrame.addRenderQueue(rq);
 396             currentFrame.scrollDx = dx;
 397             currentFrame.scrollDy = dy;
 398             // Now we have to translate "old" dirty rects that fit to the frame's
 399             // content as the content is already scrolled at the moment by webkit.
 400             if (!dirtyRects.isEmpty()) {
 401                 WCRectangle scrollRect = new WCRectangle(x, y, w, h);
 402                 for (WCRectangle r: dirtyRects) {
 403                     if (scrollRect.contains(r)) {
 404                         if (paintLog.isLoggable(Level.FINEST)) {
 405                             paintLog.log(Level.FINEST, "translating old dirty rect by the delta: " + r);
 406                         }
 407                         r.translate(dx, dy);
 408                     }
 409                 }
 410             }
 411         }
 412 
 413         // Add the dirty (not copied) rects
 414         addDirtyRect(new WCRectangle(x, dy >= 0 ? y : y + h + dy,
 415                                      w, Math.abs(dy)));
 416         addDirtyRect(new WCRectangle(dx >= 0 ? x : x + w + dx, y,
 417                                      Math.abs(dx), h - Math.abs(dy)));
 418     }
 419 
 420     // Instances of this class may not be accessed and modified concurrently
 421     // by multiple threads
 422     private static final class RenderFrame {
 423         private final List<WCRenderQueue> rqList =
 424                 new LinkedList<WCRenderQueue>();
 425         private int scrollDx, scrollDy;
 426         private final WCRectangle enclosingRect = new WCRectangle();
 427 
 428         // Called on: Event thread only
 429         private void addRenderQueue(WCRenderQueue rq) {
 430             if (rq.isEmpty()) {
 431                 return;
 432             }
 433             rqList.add(rq);
 434             WCRectangle rqRect = rq.getClip();
 435             if (enclosingRect.isEmpty()) {
 436                 enclosingRect.setFrame(rqRect.getX(), rqRect.getY(),
 437                                        rqRect.getWidth(), rqRect.getHeight());
 438             } else if (rqRect.isEmpty()) {
 439                 // do nothing
 440             } else {
 441                 WCRectangle.union(enclosingRect, rqRect, enclosingRect);
 442             }
 443         }
 444 
 445         // Called on: Event thread and Main thread
 446         private List<WCRenderQueue> getRQList() {
 447             return rqList;
 448         }
 449 
 450         // Called on: Event thread only
 451         private WCRectangle getEnclosingRect() {
 452             return enclosingRect;
 453         }
 454 
 455         // Called on: Event thread only
 456         private void drop() {
 457             for (WCRenderQueue rq : rqList) {
 458                 rq.dispose();
 459             }
 460             rqList.clear();
 461             enclosingRect.setFrame(0, 0, 0, 0);
 462             scrollDx = 0;
 463             scrollDy = 0;
 464         }
 465 
 466         @Override
 467         public String toString() {
 468             return "RenderFrame{"
 469                     + "rqList=" + rqList + ", "
 470                     + "enclosingRect=" + enclosingRect
 471                     + "}";
 472         }
 473     }
 474 
 475     // *************************************************************************
 476     // Callback API
 477     // *************************************************************************
 478 
 479     private final WebPageClient pageClient;
 480     private final UIClient uiClient;
 481     private final PolicyClient policyClient;
 482     private InputMethodClient imClient;
 483     private final List<LoadListenerClient> loadListenerClients =
 484         new LinkedList<LoadListenerClient>();
 485     private final InspectorClient inspectorClient;
 486     private final RenderTheme renderTheme;
 487     private final ScrollBarTheme scrollbarTheme;
 488 
 489     public WebPageClient getPageClient() {
 490         return pageClient;
 491     }
 492 
 493     public void setInputMethodClient(InputMethodClient imClient) {
 494         this.imClient = imClient;
 495     }
 496 
 497     public void setInputMethodState(boolean state) {
 498         if (imClient != null) {
 499             // A web page containing multiple clients is a single client from Java
 500             // Input Method Framework's viewpoint. We need to control activation and
 501             // deactivation for each text field/area here. Also, we need to control
 502             // enabling and disabling input methods here so that input method events
 503             // won't get delivered to wrong places (e.g., background).
 504             imClient.activateInputMethods(state);
 505         }
 506     }
 507 
 508     public void addLoadListenerClient(LoadListenerClient l) {
 509         if (!loadListenerClients.contains(l)) {
 510             loadListenerClients.add(l);
 511         }
 512     }
 513 
 514     private RenderTheme getRenderTheme() {
 515         return renderTheme;
 516     }
 517 
 518     private static RenderTheme fwkGetDefaultRenderTheme() {
 519         return ThemeClient.getDefaultRenderTheme();
 520     }
 521 
 522     private ScrollBarTheme getScrollBarTheme() {
 523         return scrollbarTheme;
 524     }
 525 
 526     // *************************************************************************
 527     // UI stuff API
 528     // *************************************************************************
 529 
 530     public void setBounds(int x, int y, int w, int h) {
 531         lockPage();
 532         try {
 533             log.log(Level.FINE, "setBounds: " + x + " " + y + " " + w + " " + h);
 534             if (isDisposed) {
 535                 log.log(Level.FINE, "setBounds() request for a disposed web page.");
 536                 return;
 537             }
 538             width = w;
 539             height = h;
 540             twkSetBounds(getPage(), 0, 0, w, h);
 541             // In response to the above call, WebKit will issue many
 542             // repaint requests, one of which will be meant to invalidate
 543             // the entire visible area. However, if the current scroll
 544             // offset is non-zero, that repaint request will contain
 545             // incorrect coordinates.
 546             // As of time of writing this, this problem exists in both
 547             // MiniBrowser and WinLauncher.
 548             // MiniBrowser is based on WebKit2, and WebKit2 workarounds
 549             // this problem by calling m_drawingArea->setNeedsDisplay()
 550             // for the entire visible area from within the WebKit2's
 551             // WebPage::setSize().
 552             // WinLauncher workarounds this problem by setting the main
 553             // window class style to CS_HREDRAW | CS_VREDRAW and calling
 554             // MoveWindow() with bRepaint = TRUE when resizing the web
 555             // view.
 556             // We workaround this problem by invalidating the entire
 557             // visible area here.
 558             repaintAll();
 559 
 560         } finally {
 561             unlockPage();
 562         }
 563     }
 564 
 565     public void setOpaque(long frameID, boolean isOpaque) {
 566         lockPage();
 567         try {
 568             log.log(Level.FINE, "setOpaque: " + isOpaque);
 569             if (isDisposed) {
 570                 log.log(Level.FINE, "setOpaque() request for a disposed web page.");
 571                 return;
 572             }
 573             if (!frames.contains(frameID)) {
 574                 return;
 575             }
 576             twkSetTransparent(frameID, !isOpaque);
 577 
 578         } finally {
 579             unlockPage();
 580         }
 581     }
 582 
 583     public void setBackgroundColor(long frameID, int backgroundColor) {
 584         lockPage();
 585         try {
 586             log.log(Level.FINE, "setBackgroundColor: " + backgroundColor);
 587             if (isDisposed) {
 588                 log.log(Level.FINE, "setBackgroundColor() request for a disposed web page.");
 589                 return;
 590             }
 591             if (!frames.contains(frameID)) {
 592                 return;
 593             }
 594             twkSetBackgroundColor(frameID, backgroundColor);
 595 
 596         } finally {
 597             unlockPage();
 598         }
 599     }
 600 
 601     public void setBackgroundColor(int backgroundColor) {
 602         lockPage();
 603         try {
 604             log.log(Level.FINE, "setBackgroundColor: " + backgroundColor +
 605                    " for all frames");
 606             if (isDisposed) {
 607                 log.log(Level.FINE, "setBackgroundColor() request for a disposed web page.");
 608                 return;
 609             }
 610 
 611             for (long frameID: frames) {
 612                 twkSetBackgroundColor(frameID, backgroundColor);
 613             }
 614 
 615         } finally {
 616             unlockPage();
 617         }
 618     }
 619 
 620     /*
 621      * Executed on the Event Thread.
 622      */
 623     public void updateContent(WCRectangle toPaint) {
 624         lockPage();
 625         try {
 626             ++updateContentCycleID;
 627 
 628             paintLog.log(Level.FINEST, "toPaint: {0}", toPaint);
 629             if (isDisposed) {
 630                 paintLog.fine("updateContent() request for a disposed web page.");
 631                 return;
 632             }
 633             updateDirty(toPaint);
 634 
 635         } finally {
 636             unlockPage();
 637         }
 638     }
 639 
 640     public int getUpdateContentCycleID() {
 641         return updateContentCycleID;
 642     }
 643 
 644     public boolean isRepaintPending() {
 645         lockPage();
 646         try {
 647             synchronized (frameQueue) {
 648                 return !frameQueue.isEmpty();
 649             }
 650         } finally {
 651             unlockPage();
 652         }
 653     }
 654 
 655     /*
 656      * Executed on printing thread.
 657      */
 658     public void print(WCGraphicsContext gc,
 659             final int x, final int y, final int w, final int h)
 660     {
 661         lockPage();
 662         try {
 663             final WCRenderQueue rq = WCGraphicsManager.getGraphicsManager().
 664                     createRenderQueue(new WCRectangle(x, y, w, h), true);
 665             FutureTask<Void> f = new FutureTask<Void>(() -> {
 666                 twkUpdateContent(getPage(), rq, x, y, w, h);
 667             }, null);
 668             Invoker.getInvoker().invokeOnEventThread(f);
 669 
 670             try {
 671                 // block until job is complete
 672                 f.get();
 673             } catch (ExecutionException ex) {
 674                 throw new AssertionError(ex);
 675             } catch (InterruptedException ex) {
 676                 // ignore; recovery is impossible
 677             }
 678 
 679             rq.decode(gc);
 680         } finally {
 681             unlockPage();
 682         }
 683     }
 684 
 685     /*
 686      * Executed on the Render Thread.
 687      */
 688     public void paint(WCGraphicsContext gc, int x, int y, int w, int h) {
 689         lockPage();
 690         try {
 691             if (pageClient != null && pageClient.isBackBufferSupported()) {
 692                 if (!backbuffer.validate(width, height)) {
 693                     // We need to repaint the whole page on the next turn
 694                     Invoker.getInvoker().invokeOnEventThread(() -> {
 695                         repaintAll();
 696                     });
 697                     return;
 698                 }
 699                 WCGraphicsContext bgc = backbuffer.createGraphics();
 700                 try {
 701                     paint2GC(bgc);
 702                     bgc.flush();
 703                 } finally {
 704                     backbuffer.disposeGraphics(bgc);
 705                 }
 706                 backbuffer.flush(gc, x, y, w, h);
 707             } else {
 708                 paint2GC(gc);
 709             }
 710         } finally {
 711             unlockPage();
 712         }
 713     }
 714 
 715     private void paint2GC(WCGraphicsContext gc) {
 716         paintLog.finest("Entering");
 717         gc.setFontSmoothingType(this.fontSmoothingType);
 718 
 719         List<RenderFrame> framesToRender;
 720         synchronized (frameQueue) {
 721             framesToRender = new ArrayList(frameQueue);
 722             frameQueue.clear();
 723         }
 724 
 725         paintLog.log(Level.FINEST, "Frames to render: {0}", framesToRender);
 726 
 727         for (RenderFrame frame : framesToRender) {
 728             paintLog.log(Level.FINEST, "Rendering: {0}", frame);
 729             for (WCRenderQueue rq : frame.getRQList()) {
 730                 gc.saveState();
 731                 if (rq.getClip() != null) {
 732                     gc.setClip(rq.getClip());
 733                 }
 734                 rq.decode(gc);
 735                 gc.restoreState();
 736             }
 737         }
 738         paintLog.finest("Exiting");
 739     }
 740 
 741     /*
 742      * Executed on the Event Thread.
 743      */
 744     public void dropRenderFrames() {
 745         lockPage();
 746         try {
 747             currentFrame.drop();
 748             synchronized (frameQueue) {
 749                 for (RenderFrame frame = frameQueue.poll(); frame != null; frame = frameQueue.poll()) {
 750                     frame.drop();
 751                 }
 752             }
 753         } finally {
 754             unlockPage();
 755         }
 756     }
 757 
 758     public void dispatchFocusEvent(WCFocusEvent fe) {
 759         lockPage();
 760         try {
 761             log.log(Level.FINEST, "dispatchFocusEvent: " + fe);
 762             if (isDisposed) {
 763                 log.log(Level.FINE, "Focus event for a disposed web page.");
 764                 return;
 765             }
 766             twkProcessFocusEvent(getPage(), fe.getID(), fe.getDirection());
 767 
 768         } finally {
 769             unlockPage();
 770         }
 771     }
 772 
 773     public boolean dispatchKeyEvent(WCKeyEvent ke) {
 774         lockPage();
 775         try {
 776             log.log(Level.FINEST, "dispatchKeyEvent: " + ke);
 777             if (isDisposed) {
 778                 log.log(Level.FINE, "Key event for a disposed web page.");
 779                 return false;
 780             }
 781             if (WCKeyEvent.filterEvent(ke)) {
 782                 log.log(Level.FINEST, "filtered");
 783                 return false;
 784             }
 785             return twkProcessKeyEvent(getPage(), ke.getType(), ke.getText(),
 786                                       ke.getKeyIdentifier(),
 787                                       ke.getWindowsVirtualKeyCode(),
 788                                       ke.isShiftDown(), ke.isCtrlDown(),
 789                                       ke.isAltDown(), ke.isMetaDown(), ke.getWhen() / 1000.0);
 790         } finally {
 791             unlockPage();
 792         }
 793     }
 794 
 795     public boolean dispatchMouseEvent(WCMouseEvent me) {
 796         lockPage();
 797         try {
 798             log.log(Level.FINEST, "dispatchMouseEvent: " + me.getX() + "," + me.getY());
 799             if (isDisposed) {
 800                 log.log(Level.FINE, "Mouse event for a disposed web page.");
 801                 return false;
 802             }
 803 
 804             return !isDragConfirmed() //When Webkit informes FX about drag start, it waits
 805                                       //for system DnD loop and not intereasted in
 806                                       //intermediate mouse events that can change text selection.
 807                 && twkProcessMouseEvent(getPage(), me.getID(),
 808                                         me.getButton(), me.getClickCount(),
 809                                         me.getX(), me.getY(), me.getScreenX(), me.getScreenY(),
 810                                         me.isShiftDown(), me.isControlDown(), me.isAltDown(), me.isMetaDown(), me.isPopupTrigger(),
 811                                         me.getWhen() / 1000.0);
 812         } finally {
 813             unlockPage();
 814         }
 815     }
 816 
 817     public boolean dispatchMouseWheelEvent(WCMouseWheelEvent me) {
 818         lockPage();
 819         try {
 820             log.log(Level.FINEST, "dispatchMouseWheelEvent: " + me);
 821             if (isDisposed) {
 822                 log.log(Level.FINE, "MouseWheel event for a disposed web page.");
 823                 return false;
 824             }
 825             return twkProcessMouseWheelEvent(getPage(),
 826                                              me.getX(), me.getY(), me.getScreenX(), me.getScreenY(),
 827                                              me.getDeltaX(), me.getDeltaY(),
 828                                              me.isShiftDown(), me.isControlDown(), me.isAltDown(), me.isMetaDown(),
 829                                              me.getWhen() / 1000.0);
 830         } finally {
 831             unlockPage();
 832         }
 833     }
 834 
 835     public boolean dispatchInputMethodEvent(WCInputMethodEvent ie) {
 836         lockPage();
 837         try {
 838             log.log(Level.FINEST, "dispatchInputMethodEvent: " + ie);
 839             if (isDisposed) {
 840                 log.log(Level.FINE, "InputMethod event for a disposed web page.");
 841                 return false;
 842             }
 843             switch (ie.getID()) {
 844                 case WCInputMethodEvent.INPUT_METHOD_TEXT_CHANGED:
 845                     return twkProcessInputTextChange(getPage(),
 846                                                      ie.getComposed(), ie.getCommitted(),
 847                                                      ie.getAttributes(), ie.getCaretPosition());
 848 
 849                 case WCInputMethodEvent.CARET_POSITION_CHANGED:
 850                     return twkProcessCaretPositionChange(getPage(),
 851                                                          ie.getCaretPosition());
 852             }
 853             return false;
 854 
 855         } finally {
 856             unlockPage();
 857         }
 858     }
 859 
 860     public final static int DND_DST_ENTER = 0;
 861     public final static int DND_DST_OVER = 1;
 862     public final static int DND_DST_CHANGE = 2;
 863     public final static int DND_DST_EXIT = 3;
 864     public final static int DND_DST_DROP = 4;
 865 
 866     public final static int DND_SRC_ENTER = 100;
 867     public final static int DND_SRC_OVER = 101;
 868     public final static int DND_SRC_CHANGE = 102;
 869     public final static int DND_SRC_EXIT = 103;
 870     public final static int DND_SRC_DROP = 104;
 871 
 872     public int dispatchDragOperation(
 873             int commandId,
 874             String[] mimeTypes, String[] values,
 875             int x, int y,
 876             int screenX, int screenY,
 877             int dndActionId)
 878     {
 879         lockPage();
 880         try {
 881             log.log(Level.FINEST, "dispatchDragOperation: " + x + "," + y
 882                     + " dndCommand:" + commandId
 883                     + " dndAction" + dndActionId);
 884             if (isDisposed) {
 885                 log.log(Level.FINE, "DnD event for a disposed web page.");
 886                 return 0;
 887             }
 888             return twkProcessDrag(getPage(),
 889                     commandId,
 890                     mimeTypes, values,
 891                     x, y,
 892                     screenX, screenY,
 893                     dndActionId);
 894         } finally {
 895             unlockPage();
 896         }
 897     }
 898 
 899     public void confirmStartDrag() {
 900         if (uiClient != null)
 901             uiClient.confirmStartDrag();
 902     }
 903 
 904     public boolean isDragConfirmed(){
 905         return (uiClient != null)
 906             ? uiClient.isDragConfirmed()
 907             : false;
 908     }
 909 
 910     // *************************************************************************
 911     // Input methods
 912     // *************************************************************************
 913 
 914     public int[] getClientTextLocation(int index) {
 915         lockPage();
 916         try {
 917             if (isDisposed) {
 918                 log.log(Level.FINE, "getClientTextLocation() request for a disposed web page.");
 919                 return new int[] { 0, 0, 0, 0 };
 920             }
 921             Invoker.getInvoker().checkEventThread();
 922             return twkGetTextLocation(getPage(), index);
 923 
 924         } finally {
 925             unlockPage();
 926         }
 927     }
 928 
 929     public int getClientLocationOffset(int x, int y) {
 930         lockPage();
 931         try {
 932             if (isDisposed) {
 933                 log.log(Level.FINE, "getClientLocationOffset() request for a disposed web page.");
 934                 return 0;
 935             }
 936             Invoker.getInvoker().checkEventThread();
 937             return twkGetInsertPositionOffset(getPage());
 938 
 939         } finally {
 940             unlockPage();
 941         }
 942     }
 943 
 944     public int getClientInsertPositionOffset() {
 945         lockPage();
 946         try {
 947             if (isDisposed) {
 948                 log.log(Level.FINE, "getClientInsertPositionOffset() request for a disposed web page.");
 949                 return 0;
 950             }
 951             return twkGetInsertPositionOffset(getPage());
 952 
 953         } finally {
 954             unlockPage();
 955         }
 956     }
 957 
 958     public int getClientCommittedTextLength() {
 959         lockPage();
 960         try {
 961             if (isDisposed) {
 962                 log.log(Level.FINE, "getClientCommittedTextOffset() request for a disposed web page.");
 963                 return 0;
 964             }
 965             return twkGetCommittedTextLength(getPage());
 966 
 967         } finally {
 968             unlockPage();
 969         }
 970     }
 971 
 972     public String getClientCommittedText() {
 973         lockPage();
 974         try {
 975             if (isDisposed) {
 976                 log.log(Level.FINE, "getClientCommittedText() request for a disposed web page.");
 977                 return "";
 978             }
 979             return twkGetCommittedText(getPage());
 980 
 981         } finally {
 982             unlockPage();
 983         }
 984     }
 985 
 986     public String getClientSelectedText() {
 987         lockPage();
 988         try {
 989             if (isDisposed) {
 990                 log.log(Level.FINE, "getClientSelectedText() request for a disposed web page.");
 991                 return "";
 992             }
 993             return twkGetSelectedText(getPage());
 994 
 995         } finally {
 996             unlockPage();
 997         }
 998     }
 999 
1000     // *************************************************************************
1001     // Browser API
1002     // *************************************************************************
1003 
1004     public void dispose() {
1005         lockPage();
1006         try {
1007             log.log(Level.FINER, "dispose");
1008 
1009             stop();
1010             dropRenderFrames();
1011             isDisposed = true;
1012 
1013             twkDestroyPage(pPage);
1014             pPage = 0;
1015 
1016             for (long frameID : frames) {
1017                 log.log(Level.FINE, "Undestroyed frame view: " + frameID);
1018             }
1019             frames.clear();
1020 
1021             if (backbuffer != null) {
1022                 backbuffer.deref();
1023                 backbuffer = null;
1024             }
1025         } finally {
1026             unlockPage();
1027         }
1028     }
1029 
1030     public String getName(long frameID) {
1031         lockPage();
1032         try {
1033             log.log(Level.FINE, "Get Name: frame = " + frameID);
1034             if (isDisposed) {
1035                 log.log(Level.FINE, "getName() request for a disposed web page.");
1036                 return null;
1037             }
1038             if (!frames.contains(frameID)) {
1039                 return null;
1040             }
1041             return twkGetName(frameID);
1042 
1043         } finally {
1044             unlockPage();
1045         }
1046     }
1047 
1048     public String getURL(long frameID) {
1049         lockPage();
1050         try {
1051             log.log(Level.FINE, "Get URL: frame = " + frameID);
1052             if (isDisposed) {
1053                 log.log(Level.FINE, "getURL() request for a disposed web page.");
1054                 return null;
1055             }
1056             if (!frames.contains(frameID)) {
1057                 return null;
1058             }
1059             return twkGetURL(frameID);
1060 
1061         } finally {
1062             unlockPage();
1063         }
1064     }
1065 
1066     public String getEncoding() {
1067         lockPage();
1068         try {
1069             log.log(Level.FINE, "Get encoding");
1070             if (isDisposed) {
1071                 log.log(Level.FINE, "getEncoding() request for a disposed web page.");
1072                 return null;
1073             }
1074             return twkGetEncoding(getPage());
1075 
1076         } finally {
1077             unlockPage();
1078         }
1079     }
1080 
1081     public void setEncoding(String encoding) {
1082         lockPage();
1083         try {
1084             log.log(Level.FINE, "Set encoding: encoding = " + encoding);
1085             if (isDisposed) {
1086                 log.log(Level.FINE, "setEncoding() request for a disposed web page.");
1087                 return;
1088             }
1089             if (encoding != null && !encoding.isEmpty()) {
1090                 twkSetEncoding(getPage(), encoding);
1091             }
1092 
1093         } finally {
1094             unlockPage();
1095         }
1096     }
1097 
1098     // DRT support
1099     public String getInnerText(long frameID) {
1100         lockPage();
1101         try {
1102             log.log(Level.FINE, "Get inner text: frame = " + frameID);
1103             if (isDisposed) {
1104                 log.log(Level.FINE, "getInnerText() request for a disposed web page.");
1105                 return null;
1106             }
1107             if (!frames.contains(frameID)) {
1108                 return null;
1109             }
1110             return twkGetInnerText(frameID);
1111 
1112         } finally {
1113             unlockPage();
1114         }
1115     }
1116 
1117     // DRT support
1118     public String getRenderTree(long frameID) {
1119         lockPage();
1120         try {
1121             log.log(Level.FINE, "Get render tree: frame = " + frameID);
1122             if (isDisposed) {
1123                 log.log(Level.FINE, "getRenderTree() request for a disposed web page.");
1124                 return null;
1125             }
1126             if (!frames.contains(frameID)) {
1127                 return null;
1128             }
1129             return twkGetRenderTree(frameID);
1130 
1131         } finally {
1132             unlockPage();
1133         }
1134     }
1135 
1136     // DRT support
1137     public int getUnloadEventListenersCount(long frameID) {
1138         lockPage();
1139         try {
1140             log.log(Level.FINE, "frame: " + frameID);
1141             if (isDisposed) {
1142                 log.log(Level.FINE, "request for a disposed web page.");
1143                 return 0;
1144             }
1145             if (!frames.contains(frameID)) {
1146                 return 0;
1147             }
1148             return twkGetUnloadEventListenersCount(frameID);
1149 
1150         } finally {
1151             unlockPage();
1152         }
1153     }
1154 
1155     public String getContentType(long frameID) {
1156         lockPage();
1157         try {
1158             log.log(Level.FINE, "Get content type: frame = " + frameID);
1159             if (isDisposed) {
1160                 log.log(Level.FINE, "getContentType() request for a disposed web page.");
1161                 return null;
1162             }
1163             if (!frames.contains(frameID)) {
1164                 return null;
1165             }
1166             return twkGetContentType(frameID);
1167 
1168         } finally {
1169             unlockPage();
1170         }
1171     }
1172 
1173     public String getTitle(long frameID) {
1174         lockPage();
1175         try {
1176             log.log(Level.FINE, "Get title: frame = " + frameID);
1177             if (isDisposed) {
1178                 log.log(Level.FINE, "getTitle() request for a disposed web page.");
1179                 return null;
1180             }
1181             if (!frames.contains(frameID)) {
1182                 return null;
1183             }
1184             return twkGetTitle(frameID);
1185 
1186         } finally {
1187             unlockPage();
1188         }
1189     }
1190 
1191     public WCImage getIcon(long frameID) {
1192         lockPage();
1193         try {
1194             log.log(Level.FINE, "Get icon: frame = " + frameID);
1195             if (isDisposed) {
1196                 log.log(Level.FINE, "getIcon() request for a disposed web page.");
1197                 return null;
1198             }
1199             if (!frames.contains(frameID)) {
1200                 return null;
1201             }
1202             String iconURL = twkGetIconURL(frameID);
1203             // do we need any cache for icons here?
1204             if (iconURL != null && !iconURL.isEmpty()) {
1205                 return WCGraphicsManager.getGraphicsManager().getIconImage(iconURL);
1206             }
1207             return null;
1208 
1209         } finally {
1210             unlockPage();
1211         }
1212     }
1213 
1214     public void open(final long frameID, final String url) {
1215         lockPage();
1216         try {
1217             log.log(Level.FINE, "Open URL: " + url);
1218             if (isDisposed) {
1219                 log.log(Level.FINE, "open() request for a disposed web page.");
1220                 return;
1221             }
1222             if (!frames.contains(frameID)) {
1223                 return;
1224             }
1225             if (twkIsLoading(frameID)) {
1226                 Invoker.getInvoker().postOnEventThread(() -> {
1227                     // Postpone new load request while webkit is
1228                     // about to commit the DocumentLoader from
1229                     // provisional state to committed state
1230                     twkOpen(frameID, url);
1231                 });
1232             } else {
1233                 twkOpen(frameID, url);
1234             }
1235         } finally {
1236             unlockPage();
1237         }
1238     }
1239 
1240     public void load(final long frameID, final String text, final String contentType) {
1241         lockPage();
1242         try {
1243             log.log(Level.FINE, "Load text: " + text);
1244             if (text == null) {
1245                 return;
1246             }
1247             if (isDisposed) {
1248                 log.log(Level.FINE, "load() request for a disposed web page.");
1249                 return;
1250             }
1251             if (!frames.contains(frameID)) {
1252                 return;
1253             }
1254             // TODO: handle contentType
1255             if (twkIsLoading(frameID)) {
1256                 // Postpone loading new content while webkit is
1257                 // about to commit the DocumentLoader from
1258                 // provisional state to committed state
1259                 Invoker.getInvoker().postOnEventThread(() -> {
1260                     twkLoad(frameID, text, contentType);
1261                 });
1262             } else {
1263                 twkLoad(frameID, text, contentType);
1264             }
1265         } finally {
1266             unlockPage();
1267         }
1268     }
1269 
1270     public void stop(final long frameID) {
1271         lockPage();
1272         try {
1273             log.log(Level.FINE, "Stop loading: frame = " + frameID);
1274 
1275             String url;
1276             String contentType;
1277             if (isDisposed) {
1278                 log.log(Level.FINE, "cancel() request for a disposed web page.");
1279                 return;
1280             }
1281             if (!frames.contains(frameID)) {
1282                 return;
1283             }
1284             url = twkGetURL(frameID);
1285             contentType = twkGetContentType(frameID);
1286             twkStop(frameID);
1287             // WebKit doesn't send any notifications about loading stopped,
1288             // so sending it here
1289             fireLoadEvent(frameID, LoadListenerClient.LOAD_STOPPED, url, contentType, 1.0, 0);
1290 
1291         } finally {
1292             unlockPage();
1293         }
1294     }
1295 
1296     // stops all loading synchronously
1297     public void stop() {
1298         lockPage();
1299         try {
1300             log.log(Level.FINE, "Stop loading sync");
1301             if (isDisposed) {
1302                 log.log(Level.FINE, "stopAll() request for a disposed web page.");
1303                 return;
1304             }
1305             twkStopAll(getPage());
1306 
1307         } finally {
1308             unlockPage();
1309         }
1310     }
1311 
1312     public void refresh(final long frameID) {
1313         lockPage();
1314         try {
1315             log.log(Level.FINE, "Refresh: frame = " + frameID);
1316             if (isDisposed) {
1317                 log.log(Level.FINE, "refresh() request for a disposed web page.");
1318                 return;
1319             }
1320             if (!frames.contains(frameID)) {
1321                 return;
1322             }
1323             twkRefresh(frameID);
1324 
1325         } finally {
1326             unlockPage();
1327         }
1328     }
1329 
1330     public BackForwardList createBackForwardList() {
1331         return new BackForwardList(this);
1332     }
1333 
1334     public boolean goBack() {
1335         lockPage();
1336         try {
1337             log.log(Level.FINE, "Go back");
1338             if (isDisposed) {
1339                 log.log(Level.FINE, "goBack() request for a disposed web page.");
1340                 return false;
1341             }
1342             return twkGoBackForward(getPage(), -1);
1343 
1344         } finally {
1345             unlockPage();
1346         }
1347     }
1348 
1349     public boolean goForward() {
1350         lockPage();
1351         try {
1352             log.log(Level.FINE, "Go forward");
1353             if (isDisposed) {
1354                 log.log(Level.FINE, "goForward() request for a disposed web page.");
1355                 return false;
1356             }
1357             return twkGoBackForward(getPage(), 1);
1358 
1359         } finally {
1360             unlockPage();
1361         }
1362     }
1363 
1364     public boolean copy() {
1365         lockPage();
1366         try {
1367             log.log(Level.FINE, "Copy");
1368             if (isDisposed) {
1369                 log.log(Level.FINE, "copy() request for a disposed web page.");
1370                 return false;
1371             }
1372             long frameID = getMainFrame();
1373             if (!frames.contains(frameID)) {
1374                 return false;
1375             }
1376             return twkCopy(frameID);
1377 
1378         } finally {
1379             unlockPage();
1380         }
1381     }
1382 
1383     // Find in page
1384     public boolean find(String stringToFind, boolean forward, boolean wrap, boolean matchCase) {
1385         lockPage();
1386         try {
1387             log.log(Level.FINE, "Find in page: stringToFind = " + stringToFind + ", " +
1388                     (forward ? "forward" : "backward") + (wrap ? ", wrap" : "") + (matchCase ? ", matchCase" : ""));
1389             if (isDisposed) {
1390                 log.log(Level.FINE, "find() request for a disposed web page.");
1391                 return false;
1392             }
1393             return twkFindInPage(getPage(), stringToFind, forward, wrap, matchCase);
1394 
1395         } finally {
1396             unlockPage();
1397         }
1398     }
1399 
1400     // Find in frame
1401     public boolean find(long frameID,
1402         String stringToFind, boolean forward, boolean wrap, boolean matchCase)
1403     {
1404         lockPage();
1405         try {
1406             log.log(Level.FINE, "Find in frame: stringToFind = " + stringToFind + ", " +
1407                     (forward ? "forward" : "backward") + (wrap ? ", wrap" : "") + (matchCase ? ", matchCase" : ""));
1408             if (isDisposed) {
1409                 log.log(Level.FINE, "find() request for a disposed web page.");
1410                 return false;
1411             }
1412             if (!frames.contains(frameID)) {
1413                 return false;
1414             }
1415             return twkFindInFrame(frameID, stringToFind, forward, wrap, matchCase);
1416 
1417         } finally {
1418             unlockPage();
1419         }
1420     }
1421 
1422     public void overridePreference(String key, String value) {
1423         lockPage();
1424         try {
1425             twkOverridePreference(getPage(), key, value);
1426         } finally {
1427             unlockPage();
1428         }
1429     }
1430 
1431     public void resetToConsistentStateBeforeTesting() {
1432         lockPage();
1433         try {
1434             twkResetToConsistentStateBeforeTesting(getPage());
1435         } finally {
1436             unlockPage();
1437         }
1438     }
1439 
1440     public float getZoomFactor(boolean textOnly) {
1441         lockPage();
1442         try {
1443             log.log(Level.FINE, "Get zoom factor, textOnly=" + textOnly);
1444             if (isDisposed) {
1445                 log.log(Level.FINE, "getZoomFactor() request for a disposed web page.");
1446                 return 1.0f;
1447             }
1448             long frameID = getMainFrame();
1449             if (!frames.contains(frameID)) {
1450                 return 1.0f;
1451             }
1452             return twkGetZoomFactor(frameID, textOnly);
1453         } finally {
1454             unlockPage();
1455         }
1456     }
1457 
1458     public void setZoomFactor(float zoomFactor, boolean textOnly) {
1459         lockPage();
1460         try {
1461             log.fine(String.format("Set zoom factor %.2f, textOnly=%b", zoomFactor, textOnly));
1462             if (isDisposed) {
1463                 log.log(Level.FINE, "setZoomFactor() request for a disposed web page.");
1464                 return;
1465             }
1466             long frameID = getMainFrame();
1467             if ((frameID == 0) || !frames.contains(frameID)) {
1468                 return;
1469             }
1470             twkSetZoomFactor(frameID, zoomFactor, textOnly);
1471         } finally {
1472             unlockPage();
1473         }
1474     }
1475 
1476     public void setFontSmoothingType(int fontSmoothingType) {
1477         this.fontSmoothingType = fontSmoothingType;
1478         repaintAll();
1479     }
1480 
1481     // DRT support
1482     public void reset(long frameID) {
1483         lockPage();
1484         try {
1485             log.log(Level.FINE, "Reset: frame = " + frameID);
1486             if (isDisposed) {
1487                 log.log(Level.FINE, "reset() request for a disposed web page.");
1488                 return;
1489             }
1490             if ((frameID == 0) || !frames.contains(frameID)) {
1491                 return;
1492             }
1493             twkReset(frameID);
1494 
1495         } finally {
1496             unlockPage();
1497         }
1498     }
1499 
1500     public Object executeScript(long frameID, String script) throws JSException {
1501         lockPage();
1502         try {
1503             log.log(Level.FINE, "execute script: \"" + script + "\" in frame = " + frameID);
1504             if (isDisposed) {
1505                 log.log(Level.FINE, "executeScript() request for a disposed web page.");
1506                 return null;
1507             }
1508             if ((frameID == 0) || !frames.contains(frameID)) {
1509                 return null;
1510             }
1511             return twkExecuteScript(frameID, script);
1512 
1513         } finally {
1514             unlockPage();
1515         }
1516     }
1517 
1518     public long getMainFrame() {
1519         lockPage();
1520         try {
1521             log.log(Level.FINER, "getMainFrame: page = " + pPage);
1522             if (isDisposed) {
1523                 log.log(Level.FINE, "getMainFrame() request for a disposed web page.");
1524                 return 0L;
1525             }
1526             long mainFrameID = twkGetMainFrame(getPage());
1527             log.log(Level.FINER, "Main frame = " + mainFrameID);
1528             frames.add(mainFrameID);
1529             return mainFrameID;
1530         } finally {
1531             unlockPage();
1532         }
1533     }
1534 
1535     public long getParentFrame(long childID) {
1536         lockPage();
1537         try {
1538             log.log(Level.FINE, "getParentFrame: child = " + childID);
1539             if (isDisposed) {
1540                 log.log(Level.FINE, "getParentFrame() request for a disposed web page.");
1541                 return 0L;
1542             }
1543             if (!frames.contains(childID)) {
1544                 return 0L;
1545             }
1546             return twkGetParentFrame(childID);
1547         } finally {
1548             unlockPage();
1549         }
1550     }
1551 
1552     public List<Long> getChildFrames(long parentID) {
1553         lockPage();
1554         try {
1555             log.log(Level.FINE, "getChildFrames: parent = " + parentID);
1556             if (isDisposed) {
1557                 log.log(Level.FINE, "getChildFrames() request for a disposed web page.");
1558                 return null;
1559             }
1560             if (!frames.contains(parentID)) {
1561                 return null;
1562             }
1563             long[] children = twkGetChildFrames(parentID);
1564             List<Long> childrenList = new LinkedList<Long>();
1565             for (long child : children) {
1566                 childrenList.add(Long.valueOf(child));
1567             }
1568             return childrenList;
1569         } finally {
1570             unlockPage();
1571         }
1572     }
1573 
1574     public WCRectangle getVisibleRect(long frameID) {
1575         lockPage();
1576         try {
1577             if (!frames.contains(frameID)) {
1578                 return null;
1579             }
1580             int[] arr = twkGetVisibleRect(frameID);
1581             if (arr != null) {
1582                 return new WCRectangle(arr[0], arr[1], arr[2], arr[3]);
1583             }
1584             return null;
1585         } finally {
1586             unlockPage();
1587         }
1588     }
1589 
1590     public void scrollToPosition(long frameID, WCPoint p) {
1591         lockPage();
1592         try {
1593             if (!frames.contains(frameID)) {
1594                 return;
1595             }
1596             twkScrollToPosition(frameID, p.getIntX(), p.getIntY());
1597         } finally {
1598             unlockPage();
1599         }
1600     }
1601 
1602     public WCSize getContentSize(long frameID) {
1603         lockPage();
1604         try {
1605             if (!frames.contains(frameID)) {
1606                 return null;
1607             }
1608             int[] arr = twkGetContentSize(frameID);
1609             if (arr != null) {
1610                 return new WCSize(arr[0], arr[1]);
1611             }
1612             return null;
1613         } finally {
1614             unlockPage();
1615         }
1616     }
1617 
1618     // ---- DOM ---- //
1619 
1620     public Document getDocument(long frameID) {
1621         lockPage();
1622         try {
1623             log.log(Level.FINE, "getDocument");
1624             if (isDisposed) {
1625                 log.log(Level.FINE, "getDocument() request for a disposed web page.");
1626                 return null;
1627             }
1628 
1629             if (!frames.contains(frameID)) {
1630                 return null;
1631             }
1632             return twkGetDocument(frameID);
1633         } finally {
1634             unlockPage();
1635         }
1636     }
1637 
1638     public Element getOwnerElement(long frameID) {
1639         lockPage();
1640         try {
1641             log.log(Level.FINE, "getOwnerElement");
1642             if (isDisposed) {
1643                 log.log(Level.FINE, "getOwnerElement() request for a disposed web page.");
1644                 return null;
1645             }
1646 
1647             if (!frames.contains(frameID)) {
1648                 return null;
1649             }
1650             return twkGetOwnerElement(frameID);
1651         } finally {
1652             unlockPage();
1653         }
1654     }
1655 
1656    // ---- EDITING SUPPORT ---- //
1657 
1658     public boolean executeCommand(String command, String value) {
1659         lockPage();
1660         try {
1661             if (log.isLoggable(Level.FINE)) {
1662                 log.log(Level.FINE, "command: [{0}], value: [{1}]",
1663                         new Object[] {command, value});
1664             }
1665             if (isDisposed) {
1666                 log.log(Level.FINE, "Web page is already disposed");
1667                 return false;
1668             }
1669 
1670             boolean result = twkExecuteCommand(getPage(), command, value);
1671 
1672             log.log(Level.FINE, "result: [{0}]", result);
1673             return result;
1674         } finally {
1675             unlockPage();
1676         }
1677     }
1678 
1679     public boolean queryCommandEnabled(String command) {
1680         lockPage();
1681         try {
1682             log.log(Level.FINE, "command: [{0}]", command);
1683             if (isDisposed) {
1684                 log.log(Level.FINE, "Web page is already disposed");
1685                 return false;
1686             }
1687 
1688             boolean result = twkQueryCommandEnabled(getPage(), command);
1689 
1690             log.log(Level.FINE, "result: [{0}]", result);
1691             return result;
1692         } finally {
1693             unlockPage();
1694         }
1695     }
1696 
1697     public boolean queryCommandState(String command) {
1698         lockPage();
1699         try {
1700             log.log(Level.FINE, "command: [{0}]", command);
1701             if (isDisposed) {
1702                 log.log(Level.FINE, "Web page is already disposed");
1703                 return false;
1704             }
1705 
1706             boolean result = twkQueryCommandState(getPage(), command);
1707 
1708             log.log(Level.FINE, "result: [{0}]", result);
1709             return result;
1710         } finally {
1711             unlockPage();
1712         }
1713     }
1714 
1715     public String queryCommandValue(String command) {
1716         lockPage();
1717         try {
1718             log.log(Level.FINE, "command: [{0}]", command);
1719             if (isDisposed) {
1720                 log.log(Level.FINE, "Web page is already disposed");
1721                 return null;
1722             }
1723 
1724             String result = twkQueryCommandValue(getPage(), command);
1725 
1726             log.log(Level.FINE, "result: [{0}]", result);
1727             return result;
1728         } finally {
1729             unlockPage();
1730         }
1731     }
1732 
1733     public boolean isEditable() {
1734         lockPage();
1735         try {
1736             log.log(Level.FINE, "isEditable");
1737             if (isDisposed) {
1738                 log.log(Level.FINE, "isEditable() request for a disposed web page.");
1739                 return false;
1740             }
1741 
1742             return twkIsEditable(getPage());
1743         } finally {
1744             unlockPage();
1745         }
1746     }
1747 
1748     public void setEditable(boolean editable) {
1749         lockPage();
1750         try {
1751             log.log(Level.FINE, "setEditable");
1752             if (isDisposed) {
1753                 log.log(Level.FINE, "setEditable() request for a disposed web page.");
1754                 return;
1755             }
1756 
1757             twkSetEditable(getPage(), editable);
1758         } finally {
1759             unlockPage();
1760         }
1761     }
1762 
1763     /**
1764      * @return HTML content of the frame,
1765      *         or null if frame document is absent or non-HTML.
1766      */
1767     public String getHtml(long frameID) {
1768         lockPage();
1769         try {
1770             log.log(Level.FINE, "getHtml");
1771             if (isDisposed) {
1772                 log.log(Level.FINE, "getHtml() request for a disposed web page.");
1773                 return null;
1774             }
1775             if (!frames.contains(frameID)) {
1776                 return null;
1777             }
1778             return twkGetHtml(frameID);
1779         } finally {
1780             unlockPage();
1781         }
1782     }
1783 
1784     // ---- PRINTING SUPPORT ---- //
1785 
1786     public int beginPrinting(float width, float height) {
1787         lockPage();
1788         try {
1789             if (isDisposed) {
1790                 log.warning("beginPrinting() called for a disposed web page.");
1791                 return 0;
1792             }
1793             AtomicReference<Integer> retVal = new AtomicReference<>(0);
1794             final CountDownLatch l = new CountDownLatch(1);
1795             Invoker.getInvoker().invokeOnEventThread(() -> {
1796                 try {
1797                     int nPages = twkBeginPrinting(getPage(), width, height);
1798                     retVal.set(nPages);
1799                 } finally {
1800                     l.countDown();
1801                 }
1802             });
1803 
1804             try {
1805                 l.await();
1806             } catch (InterruptedException e) {
1807                 throw new RuntimeException(e);
1808             }
1809             return retVal.get();
1810         } finally {
1811             unlockPage();
1812         }
1813     }
1814 
1815     public void endPrinting() {
1816         lockPage();
1817         try {
1818             if (isDisposed) {
1819                 log.warning("endPrinting() called for a disposed web page.");
1820                 return;
1821             }
1822             final CountDownLatch l = new CountDownLatch(1);
1823             Invoker.getInvoker().invokeOnEventThread(() -> {
1824                 try {
1825                     twkEndPrinting(getPage());
1826                 } finally {
1827                     l.countDown();
1828                 }
1829             });
1830 
1831             try {
1832                 l.await();
1833             } catch (InterruptedException e) {
1834                 throw new RuntimeException(e);
1835             }
1836         } finally {
1837             unlockPage();
1838         }
1839     }
1840 
1841     public void print(final WCGraphicsContext gc, final int pageNumber, final float width) {
1842         lockPage();
1843         try {
1844             if (isDisposed) {
1845                 log.warning("print() called for a disposed web page.");
1846                 return;
1847             }
1848             final WCRenderQueue rq = WCGraphicsManager.getGraphicsManager().
1849                     createRenderQueue(null, true);
1850             final CountDownLatch l = new CountDownLatch(1);
1851             Invoker.getInvoker().invokeOnEventThread(() -> {
1852                 try {
1853                     twkPrint(getPage(), rq, pageNumber, width);
1854                 } finally {
1855                     l.countDown();
1856                 }
1857             });
1858 
1859             try {
1860                 l.await();
1861             } catch (InterruptedException e) {
1862                 rq.dispose();
1863                 return;
1864             }
1865             rq.decode(gc);
1866         } finally {
1867             unlockPage();
1868         }
1869     }
1870 
1871     public int getPageHeight() {
1872         return getFrameHeight(getMainFrame());
1873     }
1874 
1875     public int getFrameHeight(long frameID) {
1876         lockPage();
1877         try {
1878             log.log(Level.FINE, "Get page height");
1879             if (isDisposed) {
1880                 log.log(Level.FINE, "getFrameHeight() request for a disposed web page.");
1881                 return 0;
1882             }
1883             if (!frames.contains(frameID)) {
1884                 return 0;
1885             }
1886             int height = twkGetFrameHeight(frameID);
1887             log.log(Level.FINE, "Height = " + height);
1888             return height;
1889         } finally {
1890             unlockPage();
1891         }
1892     }
1893 
1894     public float adjustFrameHeight(long frameID,
1895                                    float oldTop, float oldBottom, float bottomLimit)
1896     {
1897         lockPage();
1898         try {
1899             log.log(Level.FINE, "Adjust page height");
1900             if (isDisposed) {
1901                 log.log(Level.FINE, "adjustFrameHeight() request for a disposed web page.");
1902                 return 0;
1903             }
1904             if (!frames.contains(frameID)) {
1905                 return 0;
1906             }
1907             return twkAdjustFrameHeight(frameID, oldTop, oldBottom, bottomLimit);
1908         } finally {
1909             unlockPage();
1910         }
1911     }
1912 
1913     // ---- SETTINGS ---- //
1914 
1915     /**
1916      * Returns the usePageCache settings field.
1917      * @return {@code true} if this object uses the page cache,
1918      *         {@code false} otherwise.
1919      */
1920     public boolean getUsePageCache() {
1921         lockPage();
1922         try {
1923             return twkGetUsePageCache(getPage());
1924         } finally {
1925             unlockPage();
1926         }
1927     }
1928 
1929     /**
1930      * Sets the usePageCache settings field.
1931      * @param usePageCache {@code true} to use the page cache,
1932      *        {@code false} to not use the page cache.
1933      */
1934     public void setUsePageCache(boolean usePageCache) {
1935         lockPage();
1936         try {
1937             twkSetUsePageCache(getPage(), usePageCache);
1938         } finally {
1939             unlockPage();
1940         }
1941     }
1942 
1943     public boolean getDeveloperExtrasEnabled() {
1944         lockPage();
1945         try {
1946             boolean result = twkGetDeveloperExtrasEnabled(getPage());
1947             log.log(Level.FINE,
1948                     "Getting developerExtrasEnabled, result: [{0}]",
1949                     result);
1950             return result;
1951         } finally {
1952             unlockPage();
1953         }
1954     }
1955 
1956     public void setDeveloperExtrasEnabled(boolean enabled) {
1957         lockPage();
1958         try {
1959             log.log(Level.FINE,
1960                     "Setting developerExtrasEnabled, value: [{0}]",
1961                     enabled);
1962             twkSetDeveloperExtrasEnabled(getPage(), enabled);
1963         } finally {
1964             unlockPage();
1965         }
1966     }
1967 
1968     public boolean isJavaScriptEnabled() {
1969         lockPage();
1970         try {
1971             return twkIsJavaScriptEnabled(getPage());
1972         } finally {
1973             unlockPage();
1974         }
1975     }
1976 
1977     public void setJavaScriptEnabled(boolean enable) {
1978         lockPage();
1979         try {
1980             twkSetJavaScriptEnabled(getPage(), enable);
1981         } finally {
1982             unlockPage();
1983         }
1984     }
1985 
1986     public boolean isContextMenuEnabled() {
1987         lockPage();
1988         try {
1989             return twkIsContextMenuEnabled(getPage());
1990         } finally {
1991             unlockPage();
1992         }
1993     }
1994 
1995     public void setContextMenuEnabled(boolean enable) {
1996         lockPage();
1997         try {
1998             twkSetContextMenuEnabled(getPage(), enable);
1999         } finally {
2000             unlockPage();
2001         }
2002     }
2003 
2004     public void setUserStyleSheetLocation(String url) {
2005         lockPage();
2006         try {
2007             twkSetUserStyleSheetLocation(getPage(), url);
2008         } finally {
2009             unlockPage();
2010         }
2011     }
2012 
2013     public String getUserAgent() {
2014         lockPage();
2015         try {
2016             return twkGetUserAgent(getPage());
2017         } finally {
2018             unlockPage();
2019         }
2020     }
2021 
2022     public void setUserAgent(String userAgent) {
2023         lockPage();
2024         try {
2025             twkSetUserAgent(getPage(), userAgent);
2026         } finally {
2027             unlockPage();
2028         }
2029     }
2030 
2031     public void setLocalStorageDatabasePath(String path) {
2032         lockPage();
2033         try {
2034             twkSetLocalStorageDatabasePath(getPage(), path);
2035         } finally {
2036             unlockPage();
2037         }
2038     }
2039 
2040     public void setLocalStorageEnabled(boolean enabled) {
2041         lockPage();
2042         try {
2043             twkSetLocalStorageEnabled(getPage(), enabled);
2044         } finally {
2045             unlockPage();
2046         }
2047     }
2048 
2049     // ---- INSPECTOR SUPPORT ---- //
2050 
2051     public void connectInspectorFrontend() {
2052         lockPage();
2053         try {
2054             log.log(Level.FINE, "Connecting inspector frontend");
2055             twkConnectInspectorFrontend(getPage());
2056         } finally {
2057             unlockPage();
2058         }
2059     }
2060 
2061     public void disconnectInspectorFrontend() {
2062         lockPage();
2063         try {
2064             log.log(Level.FINE, "Disconnecting inspector frontend");
2065             twkDisconnectInspectorFrontend(getPage());
2066         } finally {
2067             unlockPage();
2068         }
2069     }
2070 
2071     public void dispatchInspectorMessageFromFrontend(String message) {
2072         lockPage();
2073         try {
2074             if (log.isLoggable(Level.FINE)) {
2075                 log.log(Level.FINE,
2076                         "Dispatching inspector message from frontend, "
2077                         + "message: [{0}]",
2078                         message);
2079             }
2080             twkDispatchInspectorMessageFromFrontend(getPage(), message);
2081         } finally {
2082             unlockPage();
2083         }
2084     }
2085 
2086     // *************************************************************************
2087     // Native callbacks
2088     // *************************************************************************
2089 
2090     private void fwkFrameCreated(long frameID) {
2091         log.log(Level.FINE, "Frame created: frame = " + frameID);
2092         if (frames.contains(frameID)) {
2093             log.log(Level.FINE, "Error in fwkFrameCreated: frame is already in frames");
2094             return;
2095         }
2096         frames.add(frameID);
2097     }
2098 
2099     private void fwkFrameDestroyed(long frameID) {
2100         log.log(Level.FINE, "Frame destroyed: frame = " + frameID);
2101         if (!frames.contains(frameID)) {
2102             log.log(Level.FINE, "Error in fwkFrameDestroyed: frame is not found in frames");
2103             return;
2104         }
2105         frames.remove(frameID);
2106     }
2107 
2108     private void fwkRepaint(int x, int y, int w, int h) {
2109         lockPage();
2110         try {
2111             if (paintLog.isLoggable(Level.FINEST)) {
2112                 paintLog.log(Level.FINEST, "x: {0}, y: {1}, w: {2}, h: {3}",
2113                         new Object[] {x, y, w, h});
2114             }
2115             addDirtyRect(new WCRectangle(x, y, w, h));
2116         } finally {
2117             unlockPage();
2118         }
2119     }
2120 
2121     private void fwkScroll(int x, int y, int w, int h, int deltaX, int deltaY) {
2122         if (paintLog.isLoggable(Level.FINEST)) {
2123             paintLog.finest("Scroll: " + x + " " + y + " " + w + " " + h + "  " + deltaX + " " + deltaY);
2124         }
2125         if (pageClient == null || !pageClient.isBackBufferSupported()) {
2126             paintLog.finest("blit scrolling is switched off");
2127             // TODO: check why we return void, not boolean (see ScrollView::m_canBlitOnScroll)
2128             return;
2129         }
2130         scroll(x, y, w, h, deltaX, deltaY);
2131     }
2132 
2133     private void fwkTransferFocus(boolean forward) {
2134         log.log(Level.FINER, "Transfer focus " + (forward ? "forward" : "backward"));
2135 
2136         if (pageClient != null) {
2137             pageClient.transferFocus(forward);
2138         }
2139     }
2140 
2141     private void fwkSetCursor(long id) {
2142         log.log(Level.FINER, "Set cursor: " + id);
2143 
2144         if (pageClient != null) {
2145             pageClient.setCursor(id);
2146         }
2147     }
2148 
2149     private void fwkSetFocus(boolean focus) {
2150         log.log(Level.FINER, "Set focus: " + (focus ? "true" : "false"));
2151 
2152         if (pageClient != null) {
2153             pageClient.setFocus(focus);
2154         }
2155     }
2156 
2157     private void fwkSetTooltip(String tooltip) {
2158         log.log(Level.FINER, "Set tooltip: " + tooltip);
2159 
2160         if (pageClient != null) {
2161             pageClient.setTooltip(tooltip);
2162         }
2163     }
2164 
2165     private void fwkPrint() {
2166         log.log(Level.FINER, "Print");
2167 
2168         if (uiClient != null) {
2169             uiClient.print();
2170         }
2171     }
2172 
2173     private void fwkSetRequestURL(long pFrame, int id, String url) {
2174         log.log(Level.FINER, "Set request URL: id = " + id + ", url = " + url);
2175 
2176         synchronized (requestURLs) {
2177             requestURLs.put(id, url);
2178         }
2179     }
2180 
2181     private void fwkRemoveRequestURL(long pFrame, int id) {
2182         log.log(Level.FINER, "Set request URL: id = " + id);
2183 
2184         synchronized (requestURLs) {
2185             requestURLs.remove(id);
2186             requestStarted.remove(id);
2187         }
2188     }
2189 
2190     private WebPage fwkCreateWindow(
2191             boolean menu, boolean status, boolean toolbar, boolean resizable) {
2192         log.log(Level.FINER, "Create window");
2193 
2194         if (uiClient != null) {
2195             return uiClient.createPage(menu, status, toolbar, resizable);
2196         }
2197         return null;
2198     }
2199 
2200     private void fwkShowWindow() {
2201         log.log(Level.FINER, "Show window");
2202 
2203         if (uiClient != null) {
2204             uiClient.showView();
2205         }
2206     }
2207 
2208     private void fwkCloseWindow() {
2209         log.log(Level.FINER, "Close window");
2210 
2211         if (permitCloseWindowAction()) {
2212             if (uiClient != null) {
2213                 uiClient.closePage();
2214             }
2215         }
2216     }
2217 
2218     private WCRectangle fwkGetWindowBounds() {
2219         log.log(Level.FINE, "Get window bounds");
2220 
2221         if (uiClient != null) {
2222             WCRectangle bounds = uiClient.getViewBounds();
2223             if (bounds != null) {
2224                 return bounds;
2225             }
2226         }
2227         return fwkGetPageBounds();
2228     }
2229 
2230     private void fwkSetWindowBounds(int x, int y, int w, int h) {
2231         log.log(Level.FINER, "Set window bounds: " + x + " " + y + " " + w + " " + h);
2232 
2233         if (uiClient != null) {
2234             uiClient.setViewBounds(new WCRectangle(x, y, w, h));
2235         }
2236     }
2237 
2238     private WCRectangle fwkGetPageBounds() {
2239         log.log(Level.FINER, "Get page bounds");
2240         return new WCRectangle(0, 0, width, height);
2241     }
2242 
2243     private void fwkSetScrollbarsVisible(boolean visible) {
2244         // TODO: handle this request internally
2245     }
2246 
2247     private void fwkSetStatusbarText(String text) {
2248         log.log(Level.FINER, "Set statusbar text: " + text);
2249 
2250         if (uiClient != null) {
2251             uiClient.setStatusbarText(text);
2252         }
2253     }
2254 
2255     private String[] fwkChooseFile(String initialFileName, boolean multiple, String mimeFilters) {
2256         log.log(Level.FINER, "Choose file, initial=" + initialFileName);
2257 
2258         return uiClient != null
2259                 ? uiClient.chooseFile(initialFileName, multiple, mimeFilters)
2260                 : null;
2261     }
2262 
2263     private void fwkStartDrag(
2264           Object image,
2265           int imageOffsetX, int imageOffsetY,
2266           int eventPosX, int eventPosY,
2267           String[] mimeTypes, Object[] values,
2268           boolean isImageSource)
2269     {
2270         log.log(Level.FINER, "Start drag: ");
2271         if (uiClient != null) {
2272             uiClient.startDrag(
2273                   WCImage.getImage(image),
2274                   imageOffsetX, imageOffsetY,
2275                   eventPosX, eventPosY,
2276                   mimeTypes, values,
2277                   isImageSource);
2278         }
2279     }
2280 
2281     private WCPoint fwkScreenToWindow(WCPoint ptScreen) {
2282         log.log(Level.FINER, "fwkScreenToWindow");
2283 
2284         if (pageClient != null) {
2285             return pageClient.screenToWindow(ptScreen);
2286         }
2287         return ptScreen;
2288     }
2289 
2290     private WCPoint fwkWindowToScreen(WCPoint ptWindow) {
2291         log.log(Level.FINER, "fwkWindowToScreen");
2292 
2293         if (pageClient != null) {
2294             return pageClient.windowToScreen(ptWindow);
2295         }
2296         return ptWindow;
2297     }
2298 
2299 
2300     private void fwkAlert(String text) {
2301         log.log(Level.FINE, "JavaScript alert(): text = " + text);
2302 
2303         if (uiClient != null) {
2304             uiClient.alert(text);
2305         }
2306     }
2307 
2308     private boolean fwkConfirm(String text) {
2309         log.log(Level.FINE, "JavaScript confirm(): text = " + text);
2310 
2311         if (uiClient != null) {
2312             return uiClient.confirm(text);
2313         }
2314         return false;
2315     }
2316 
2317     private String fwkPrompt(String text, String defaultValue) {
2318         log.log(Level.FINE, "JavaScript prompt(): text = " + text + ", default = " + defaultValue);
2319 
2320         if (uiClient != null) {
2321             return uiClient.prompt(text, defaultValue);
2322         }
2323         return null;
2324     }
2325 
2326     private boolean fwkCanRunBeforeUnloadConfirmPanel() {
2327         log.log(Level.FINE, "JavaScript canRunBeforeUnloadConfirmPanel()");
2328 
2329         if (uiClient != null) {
2330             return uiClient.canRunBeforeUnloadConfirmPanel();
2331         }
2332         return false;
2333     }
2334 
2335     private boolean fwkRunBeforeUnloadConfirmPanel(String message) {
2336         log.log(Level.FINE, "JavaScript runBeforeUnloadConfirmPanel(): message = " + message);
2337 
2338         if (uiClient != null) {
2339             return uiClient.runBeforeUnloadConfirmPanel(message);
2340         }
2341         return false;
2342     }
2343 
2344     private void fwkAddMessageToConsole(String message, int lineNumber,
2345             String sourceId)
2346     {
2347         log.log(Level.FINE, "fwkAddMessageToConsole(): message = " + message
2348                 + ", lineNumber = " + lineNumber + ", sourceId = " + sourceId);
2349         if (pageClient != null) {
2350             pageClient.addMessageToConsole(message, lineNumber, sourceId);
2351         }
2352     }
2353 
2354     private void fwkFireLoadEvent(long frameID, int state,
2355                                   String url, String contentType,
2356                                   double progress, int errorCode)
2357     {
2358         log.log(Level.FINER, "Load event: pFrame = " + frameID + ", state = " + state +
2359                 ", url = " + url + ", contenttype=" + contentType +
2360                 ", progress = " + progress + ", error = " + errorCode);
2361 
2362         fireLoadEvent(frameID, state, url, contentType, progress, errorCode);
2363     }
2364 
2365     private void fwkFireResourceLoadEvent(long frameID, int state,
2366                                           int id, String contentType,
2367                                           double progress, int errorCode)
2368     {
2369         log.log(Level.FINER, "Resource load event: pFrame = " + frameID + ", state = " + state +
2370                 ", id = " + id + ", contenttype=" + contentType +
2371                 ", progress = " + progress + ", error = " + errorCode);
2372 
2373         String url = requestURLs.get(id);
2374         if (url == null) {
2375             log.log(Level.FINE, "Error in fwkFireResourceLoadEvent: unknown request id " + id);
2376             return;
2377         }
2378 
2379         int eventState = state;
2380         // convert second and all subsequent STARTED into REDIRECTED
2381         if (state == LoadListenerClient.RESOURCE_STARTED) {
2382             if (requestStarted.contains(id)) {
2383                 eventState = LoadListenerClient.RESOURCE_REDIRECTED;
2384             } else {
2385                 requestStarted.add(id);
2386             }
2387         }
2388 
2389         fireResourceLoadEvent(frameID, eventState, url, contentType, progress, errorCode);
2390     }
2391 
2392     private boolean fwkPermitNavigateAction(long pFrame, String url) {
2393         log.log(Level.FINE, "Policy: permit NAVIGATE: pFrame = " + pFrame + ", url = " + url);
2394 
2395         if (policyClient != null) {
2396             return policyClient.permitNavigateAction(pFrame, str2url(url));
2397         }
2398         return true;
2399     }
2400 
2401     private boolean fwkPermitRedirectAction(long pFrame, String url) {
2402         log.log(Level.FINE, "Policy: permit REDIRECT: pFrame = " + pFrame + ", url = " + url);
2403 
2404         if (policyClient != null) {
2405             return policyClient.permitRedirectAction(pFrame, str2url(url));
2406         }
2407         return true;
2408     }
2409 
2410     private boolean fwkPermitAcceptResourceAction(long pFrame, String url) {
2411         log.log(Level.FINE, "Policy: permit ACCEPT_RESOURCE: pFrame + " + pFrame + ", url = " + url);
2412 
2413         if (policyClient != null) {
2414             return policyClient.permitAcceptResourceAction(pFrame, str2url(url));
2415         }
2416         return true;
2417     }
2418 
2419     private boolean fwkPermitSubmitDataAction(long pFrame, String url,
2420                                               String httpMethod, boolean isSubmit)
2421     {
2422         log.log(Level.FINE, "Policy: permit " + (isSubmit ? "" : "RE") + "SUBMIT_DATA: pFrame = " +
2423                 pFrame + ", url = " + url + ", httpMethod = " + httpMethod);
2424 
2425         if (policyClient != null) {
2426             if (isSubmit) {
2427                 return policyClient.permitSubmitDataAction(pFrame, str2url(url), httpMethod);
2428             } else {
2429                 return policyClient.permitResubmitDataAction(pFrame, str2url(url), httpMethod);
2430             }
2431         }
2432         return true;
2433     }
2434 
2435     private boolean fwkPermitEnableScriptsAction(long pFrame, String url) {
2436         log.log(Level.FINE, "Policy: permit ENABLE_SCRIPTS: pFrame + " + pFrame + ", url = " + url);
2437 
2438         if (policyClient != null) {
2439             return policyClient.permitEnableScriptsAction(pFrame, str2url(url));
2440         }
2441         return true;
2442     }
2443 
2444     private boolean fwkPermitNewWindowAction(long pFrame, String url) {
2445         log.log(Level.FINE, "Policy: permit NEW_PAGE: pFrame = " + pFrame + ", url = " + url);
2446 
2447         if (policyClient != null) {
2448             return policyClient.permitNewPageAction(pFrame, str2url(url));
2449         }
2450         return true;
2451     }
2452 
2453     // Called from fwkCloseWindow, that's why no "fwk" prefix
2454     private boolean permitCloseWindowAction() {
2455         log.log(Level.FINE, "Policy: permit CLOSE_PAGE");
2456 
2457         if (policyClient != null) {
2458             // Unfortunately, webkit doesn't provide an information about what
2459             // web frame initiated close window request, so using main frame here
2460             return policyClient.permitClosePageAction(getMainFrame());
2461         }
2462         return true;
2463     }
2464 
2465     private void fwkRepaintAll() {
2466         log.log(Level.FINE, "Repainting the entire page");
2467         repaintAll();
2468     }
2469 
2470     private boolean fwkSendInspectorMessageToFrontend(String message) {
2471         if (log.isLoggable(Level.FINE)) {
2472             log.log(Level.FINE,
2473                     "Sending inspector message to frontend, message: [{0}]",
2474                     message);
2475         }
2476         boolean result = false;
2477         if (inspectorClient != null) {
2478             log.log(Level.FINE, "Invoking inspector client");
2479             result = inspectorClient.sendMessageToFrontend(message);
2480         }
2481         if (log.isLoggable(Level.FINE)) {
2482             log.log(Level.FINE, "Result: [{0}]", result);
2483         }
2484         return result;
2485     }
2486 
2487     // ---- DumpRenderTree support ---- //
2488 
2489     public static int getWorkerThreadCount() {
2490         return twkWorkerThreadCount();
2491     }
2492 
2493     private static native int twkWorkerThreadCount();
2494 
2495     private void fwkDidClearWindowObject(long pContext, long pWindowObject) {
2496         if (pageClient != null) {
2497             pageClient.didClearWindowObject(pContext, pWindowObject);
2498         }
2499     }
2500 
2501     // *************************************************************************
2502     // Private methods
2503     // *************************************************************************
2504 
2505     private URL str2url(String url) {
2506         try {
2507             return newURL(url);
2508         } catch (MalformedURLException ex) {
2509             log.log(Level.FINE, "Exception while converting \"" + url + "\" to URL", ex);
2510         }
2511         return null;
2512     }
2513 
2514     private void fireLoadEvent(long frameID, int state, String url,
2515             String contentType, double progress, int errorCode)
2516     {
2517         for (LoadListenerClient l : loadListenerClients) {
2518             l.dispatchLoadEvent(frameID, state, url, contentType, progress, errorCode);
2519         }
2520     }
2521 
2522     private void fireResourceLoadEvent(long frameID, int state, String url,
2523             String contentType, double progress, int errorCode)
2524     {
2525         for (LoadListenerClient l : loadListenerClients) {
2526             l.dispatchResourceLoadEvent(frameID, state, url, contentType, progress, errorCode);
2527         }
2528     }
2529 
2530     private void repaintAll() {
2531         dirtyRects.clear();
2532         addDirtyRect(new WCRectangle(0, 0, width, height));
2533     }
2534 
2535     // Package scope method for testing
2536     int test_getFramesCount() {
2537         return frames.size();
2538     }
2539 
2540     // *************************************************************************
2541     // Native methods
2542     // *************************************************************************
2543 
2544     private static native void twkInitWebCore(boolean useJIT, boolean useDFGJIT);
2545     private native long twkCreatePage(boolean editable);
2546     private native void twkInit(long pPage, boolean usePlugins, float devicePixelScale);
2547     private native void twkDestroyPage(long pPage);
2548 
2549     private native long twkGetMainFrame(long pPage);
2550     private native long twkGetParentFrame(long pFrame);
2551     private native long[] twkGetChildFrames(long pFrame);
2552 
2553     private native String twkGetName(long pFrame);
2554     private native String twkGetURL(long pFrame);
2555     private native String twkGetInnerText(long pFrame);
2556     private native String twkGetRenderTree(long pFrame);
2557     private native String twkGetContentType(long pFrame);
2558     private native String twkGetTitle(long pFrame);
2559     private native String twkGetIconURL(long pFrame);
2560     private native static Document twkGetDocument(long pFrame);
2561     private native static Element twkGetOwnerElement(long pFrame);
2562 
2563     private native void twkOpen(long pFrame, String url);
2564     private native void twkOverridePreference(long pPage, String key, String value);
2565     private native void twkResetToConsistentStateBeforeTesting(long pPage);
2566     private native void twkLoad(long pFrame, String text, String contentType);
2567     private native boolean twkIsLoading(long pFrame);
2568     private native void twkStop(long pFrame);
2569     private native void twkStopAll(long pPage); // sync
2570     private native void twkRefresh(long pFrame);
2571 
2572     private native boolean twkGoBackForward(long pPage, int distance);
2573 
2574     private native boolean twkCopy(long pFrame);
2575     private native boolean twkFindInPage(long pPage,
2576                                          String stringToFind, boolean forward,
2577                                          boolean wrap, boolean matchCase);
2578     private native boolean twkFindInFrame(long pFrame,
2579                                           String stringToFind, boolean forward,
2580                                           boolean wrap, boolean matchCase);
2581 
2582     private native float twkGetZoomFactor(long pFrame, boolean textOnly);
2583     private native void twkSetZoomFactor(long pFrame, float zoomFactor, boolean textOnly);
2584 
2585     private native Object twkExecuteScript(long pFrame, String script);
2586 
2587     private native void twkReset(long pFrame);
2588 
2589     private native int twkGetFrameHeight(long pFrame);
2590     private native int twkBeginPrinting(long pPage, float width, float height);
2591     private native void twkEndPrinting(long pPage);
2592     private native void twkPrint(long pPage, WCRenderQueue gc, int pageNumber, float width);
2593     private native float twkAdjustFrameHeight(long pFrame, float oldTop, float oldBottom, float bottomLimit);
2594 
2595     private native int[] twkGetVisibleRect(long pFrame);
2596     private native void twkScrollToPosition(long pFrame, int x, int y);
2597     private native int[] twkGetContentSize(long pFrame);
2598     private native void twkSetTransparent(long pFrame, boolean isTransparent);
2599     private native void twkSetBackgroundColor(long pFrame, int backgroundColor);
2600 
2601     private native void twkSetBounds(long pPage, int x, int y, int w, int h);
2602     private native void twkPrePaint(long pPage);
2603     private native void twkUpdateContent(long pPage, WCRenderQueue rq, int x, int y, int w, int h);
2604     private native void twkPostPaint(long pPage, WCRenderQueue rq,
2605                                      int x, int y, int w, int h);
2606 
2607     private native String twkGetEncoding(long pPage);
2608     private native void twkSetEncoding(long pPage, String encoding);
2609 
2610     private native void twkProcessFocusEvent(long pPage, int id, int direction);
2611     private native boolean twkProcessKeyEvent(long pPage, int type, String text,
2612                                               String keyIdentifier,
2613                                               int windowsVirtualKeyCode,
2614                                               boolean shift, boolean ctrl,
2615                                               boolean alt, boolean meta, double when);
2616     private native boolean twkProcessMouseEvent(long pPage, int id,
2617                                                 int button, int clickCount,
2618                                                 int x, int y, int sx, int sy,
2619                                                 boolean shift, boolean control, boolean alt, boolean meta,
2620                                                 boolean popupTrigger, double when);
2621     private native boolean twkProcessMouseWheelEvent(long pPage,
2622                                                      int x, int y, int sx, int sy,
2623                                                      float dx, float dy,
2624                                                      boolean shift, boolean control, boolean alt, boolean meta,
2625                                                      double when);
2626     private native boolean twkProcessInputTextChange(long pPage, String committed, String composed,
2627                                                      int[] attributes, int caretPosition);
2628     private native boolean twkProcessCaretPositionChange(long pPage, int caretPosition);
2629     private native int[] twkGetTextLocation(long pPage, int charIndex);
2630     private native int twkGetInsertPositionOffset(long pPage);
2631     private native int twkGetCommittedTextLength(long pPage);
2632     private native String twkGetCommittedText(long pPage);
2633     private native String twkGetSelectedText(long pPage);
2634 
2635     private native int twkProcessDrag(long page,
2636             int commandId,
2637             String[] mimeTypes, String[] values,
2638             int x, int y,
2639             int screenX, int screenY,
2640             int dndActionId);
2641 
2642     private native boolean twkExecuteCommand(long page, String command,
2643                                              String value);
2644     private native boolean twkQueryCommandEnabled(long page, String command);
2645     private native boolean twkQueryCommandState(long page, String command);
2646     private native String twkQueryCommandValue(long page, String command);
2647     private native boolean twkIsEditable(long page);
2648     private native void twkSetEditable(long page, boolean editable);
2649     private native String twkGetHtml(long pFrame);
2650 
2651     private native boolean twkGetUsePageCache(long page);
2652     private native void twkSetUsePageCache(long page, boolean usePageCache);
2653     private native boolean twkGetDeveloperExtrasEnabled(long page);
2654     private native void twkSetDeveloperExtrasEnabled(long page,
2655                                                      boolean enabled);
2656     private native boolean twkIsJavaScriptEnabled(long page);
2657     private native void twkSetJavaScriptEnabled(long page, boolean enable);
2658     private native boolean twkIsContextMenuEnabled(long page);
2659     private native void twkSetContextMenuEnabled(long page, boolean enable);
2660     private native void twkSetUserStyleSheetLocation(long page, String url);
2661     private native String twkGetUserAgent(long page);
2662     private native void twkSetUserAgent(long page, String userAgent);
2663     private native void twkSetLocalStorageDatabasePath(long page, String path);
2664     private native void twkSetLocalStorageEnabled(long page, boolean enabled);
2665 
2666     private native int twkGetUnloadEventListenersCount(long pFrame);
2667 
2668     private native void twkConnectInspectorFrontend(long pPage);
2669     private native void twkDisconnectInspectorFrontend(long pPage);
2670     private native void twkDispatchInspectorMessageFromFrontend(long pPage,
2671                                                                 String message);
2672     private static native void twkDoJSCGarbageCollection();
2673 }