1 /*
   2  * Copyright (c) 2011, 2016, 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             return twkGetTextLocation(getPage(), index);
 922 
 923         } finally {
 924             unlockPage();
 925         }
 926     }
 927 
 928     public int getClientLocationOffset(int x, int y) {
 929         lockPage();
 930         try {
 931             if (isDisposed) {
 932                 log.log(Level.FINE, "getClientLocationOffset() request for a disposed web page.");
 933                 return 0;
 934             }
 935             return twkGetInsertPositionOffset(getPage());
 936 
 937         } finally {
 938             unlockPage();
 939         }
 940     }
 941 
 942     public int getClientInsertPositionOffset() {
 943         lockPage();
 944         try {
 945             if (isDisposed) {
 946                 log.log(Level.FINE, "getClientInsertPositionOffset() request for a disposed web page.");
 947                 return 0;
 948             }
 949             return twkGetInsertPositionOffset(getPage());
 950 
 951         } finally {
 952             unlockPage();
 953         }
 954     }
 955 
 956     public int getClientCommittedTextLength() {
 957         lockPage();
 958         try {
 959             if (isDisposed) {
 960                 log.log(Level.FINE, "getClientCommittedTextOffset() request for a disposed web page.");
 961                 return 0;
 962             }
 963             return twkGetCommittedTextLength(getPage());
 964 
 965         } finally {
 966             unlockPage();
 967         }
 968     }
 969 
 970     public String getClientCommittedText() {
 971         lockPage();
 972         try {
 973             if (isDisposed) {
 974                 log.log(Level.FINE, "getClientCommittedText() request for a disposed web page.");
 975                 return "";
 976             }
 977             return twkGetCommittedText(getPage());
 978 
 979         } finally {
 980             unlockPage();
 981         }
 982     }
 983 
 984     public String getClientSelectedText() {
 985         lockPage();
 986         try {
 987             if (isDisposed) {
 988                 log.log(Level.FINE, "getClientSelectedText() request for a disposed web page.");
 989                 return "";
 990             }
 991             return twkGetSelectedText(getPage());
 992 
 993         } finally {
 994             unlockPage();
 995         }
 996     }
 997 
 998     // *************************************************************************
 999     // Browser API
1000     // *************************************************************************
1001 
1002     public void dispose() {
1003         lockPage();
1004         try {
1005             log.log(Level.FINER, "dispose");
1006 
1007             stop();
1008             dropRenderFrames();
1009             isDisposed = true;
1010 
1011             twkDestroyPage(pPage);
1012             pPage = 0;
1013 
1014             for (long frameID : frames) {
1015                 log.log(Level.FINE, "Undestroyed frame view: " + frameID);
1016             }
1017             frames.clear();
1018 
1019             if (backbuffer != null) {
1020                 backbuffer.deref();
1021                 backbuffer = null;
1022             }
1023         } finally {
1024             unlockPage();
1025         }
1026     }
1027 
1028     public String getName(long frameID) {
1029         lockPage();
1030         try {
1031             log.log(Level.FINE, "Get Name: frame = " + frameID);
1032             if (isDisposed) {
1033                 log.log(Level.FINE, "getName() request for a disposed web page.");
1034                 return null;
1035             }
1036             if (!frames.contains(frameID)) {
1037                 return null;
1038             }
1039             return twkGetName(frameID);
1040 
1041         } finally {
1042             unlockPage();
1043         }
1044     }
1045 
1046     public String getURL(long frameID) {
1047         lockPage();
1048         try {
1049             log.log(Level.FINE, "Get URL: frame = " + frameID);
1050             if (isDisposed) {
1051                 log.log(Level.FINE, "getURL() request for a disposed web page.");
1052                 return null;
1053             }
1054             if (!frames.contains(frameID)) {
1055                 return null;
1056             }
1057             return twkGetURL(frameID);
1058 
1059         } finally {
1060             unlockPage();
1061         }
1062     }
1063 
1064     public String getEncoding() {
1065         lockPage();
1066         try {
1067             log.log(Level.FINE, "Get encoding");
1068             if (isDisposed) {
1069                 log.log(Level.FINE, "getEncoding() request for a disposed web page.");
1070                 return null;
1071             }
1072             return twkGetEncoding(getPage());
1073 
1074         } finally {
1075             unlockPage();
1076         }
1077     }
1078 
1079     public void setEncoding(String encoding) {
1080         lockPage();
1081         try {
1082             log.log(Level.FINE, "Set encoding: encoding = " + encoding);
1083             if (isDisposed) {
1084                 log.log(Level.FINE, "setEncoding() request for a disposed web page.");
1085                 return;
1086             }
1087             if (encoding != null && !encoding.isEmpty()) {
1088                 twkSetEncoding(getPage(), encoding);
1089             }
1090 
1091         } finally {
1092             unlockPage();
1093         }
1094     }
1095 
1096     // DRT support
1097     public String getInnerText(long frameID) {
1098         lockPage();
1099         try {
1100             log.log(Level.FINE, "Get inner text: frame = " + frameID);
1101             if (isDisposed) {
1102                 log.log(Level.FINE, "getInnerText() request for a disposed web page.");
1103                 return null;
1104             }
1105             if (!frames.contains(frameID)) {
1106                 return null;
1107             }
1108             return twkGetInnerText(frameID);
1109 
1110         } finally {
1111             unlockPage();
1112         }
1113     }
1114 
1115     // DRT support
1116     public String getRenderTree(long frameID) {
1117         lockPage();
1118         try {
1119             log.log(Level.FINE, "Get render tree: frame = " + frameID);
1120             if (isDisposed) {
1121                 log.log(Level.FINE, "getRenderTree() request for a disposed web page.");
1122                 return null;
1123             }
1124             if (!frames.contains(frameID)) {
1125                 return null;
1126             }
1127             return twkGetRenderTree(frameID);
1128 
1129         } finally {
1130             unlockPage();
1131         }
1132     }
1133 
1134     // DRT support
1135     public int getUnloadEventListenersCount(long frameID) {
1136         lockPage();
1137         try {
1138             log.log(Level.FINE, "frame: " + frameID);
1139             if (isDisposed) {
1140                 log.log(Level.FINE, "request for a disposed web page.");
1141                 return 0;
1142             }
1143             if (!frames.contains(frameID)) {
1144                 return 0;
1145             }
1146             return twkGetUnloadEventListenersCount(frameID);
1147 
1148         } finally {
1149             unlockPage();
1150         }
1151     }
1152 
1153     public String getContentType(long frameID) {
1154         lockPage();
1155         try {
1156             log.log(Level.FINE, "Get content type: frame = " + frameID);
1157             if (isDisposed) {
1158                 log.log(Level.FINE, "getContentType() request for a disposed web page.");
1159                 return null;
1160             }
1161             if (!frames.contains(frameID)) {
1162                 return null;
1163             }
1164             return twkGetContentType(frameID);
1165 
1166         } finally {
1167             unlockPage();
1168         }
1169     }
1170 
1171     public String getTitle(long frameID) {
1172         lockPage();
1173         try {
1174             log.log(Level.FINE, "Get title: frame = " + frameID);
1175             if (isDisposed) {
1176                 log.log(Level.FINE, "getTitle() request for a disposed web page.");
1177                 return null;
1178             }
1179             if (!frames.contains(frameID)) {
1180                 return null;
1181             }
1182             return twkGetTitle(frameID);
1183 
1184         } finally {
1185             unlockPage();
1186         }
1187     }
1188 
1189     public WCImage getIcon(long frameID) {
1190         lockPage();
1191         try {
1192             log.log(Level.FINE, "Get icon: frame = " + frameID);
1193             if (isDisposed) {
1194                 log.log(Level.FINE, "getIcon() request for a disposed web page.");
1195                 return null;
1196             }
1197             if (!frames.contains(frameID)) {
1198                 return null;
1199             }
1200             String iconURL = twkGetIconURL(frameID);
1201             // do we need any cache for icons here?
1202             if (iconURL != null && !iconURL.isEmpty()) {
1203                 return WCGraphicsManager.getGraphicsManager().getIconImage(iconURL);
1204             }
1205             return null;
1206 
1207         } finally {
1208             unlockPage();
1209         }
1210     }
1211 
1212     public void open(final long frameID, final String url) {
1213         lockPage();
1214         try {
1215             log.log(Level.FINE, "Open URL: " + url);
1216             if (isDisposed) {
1217                 log.log(Level.FINE, "open() request for a disposed web page.");
1218                 return;
1219             }
1220             if (!frames.contains(frameID)) {
1221                 return;
1222             }
1223             if (twkIsLoading(frameID)) {
1224                 Invoker.getInvoker().postOnEventThread(() -> {
1225                     // Postpone new load request while webkit is
1226                     // about to commit the DocumentLoader from
1227                     // provisional state to committed state
1228                     twkOpen(frameID, url);
1229                 });
1230             } else {
1231                 twkOpen(frameID, url);
1232             }
1233         } finally {
1234             unlockPage();
1235         }
1236     }
1237 
1238     public void load(final long frameID, final String text, final String contentType) {
1239         lockPage();
1240         try {
1241             log.log(Level.FINE, "Load text: " + text);
1242             if (text == null) {
1243                 return;
1244             }
1245             if (isDisposed) {
1246                 log.log(Level.FINE, "load() request for a disposed web page.");
1247                 return;
1248             }
1249             if (!frames.contains(frameID)) {
1250                 return;
1251             }
1252             // TODO: handle contentType
1253             if (twkIsLoading(frameID)) {
1254                 // Postpone loading new content while webkit is
1255                 // about to commit the DocumentLoader from
1256                 // provisional state to committed state
1257                 Invoker.getInvoker().postOnEventThread(() -> {
1258                     twkLoad(frameID, text, contentType);
1259                 });
1260             } else {
1261                 twkLoad(frameID, text, contentType);
1262             }
1263         } finally {
1264             unlockPage();
1265         }
1266     }
1267 
1268     public void stop(final long frameID) {
1269         lockPage();
1270         try {
1271             log.log(Level.FINE, "Stop loading: frame = " + frameID);
1272 
1273             String url;
1274             String contentType;
1275             if (isDisposed) {
1276                 log.log(Level.FINE, "cancel() request for a disposed web page.");
1277                 return;
1278             }
1279             if (!frames.contains(frameID)) {
1280                 return;
1281             }
1282             url = twkGetURL(frameID);
1283             contentType = twkGetContentType(frameID);
1284             twkStop(frameID);
1285             // WebKit doesn't send any notifications about loading stopped,
1286             // so sending it here
1287             fireLoadEvent(frameID, LoadListenerClient.LOAD_STOPPED, url, contentType, 1.0, 0);
1288 
1289         } finally {
1290             unlockPage();
1291         }
1292     }
1293 
1294     // stops all loading synchronously
1295     public void stop() {
1296         lockPage();
1297         try {
1298             log.log(Level.FINE, "Stop loading sync");
1299             if (isDisposed) {
1300                 log.log(Level.FINE, "stopAll() request for a disposed web page.");
1301                 return;
1302             }
1303             twkStopAll(getPage());
1304 
1305         } finally {
1306             unlockPage();
1307         }
1308     }
1309 
1310     public void refresh(final long frameID) {
1311         lockPage();
1312         try {
1313             log.log(Level.FINE, "Refresh: frame = " + frameID);
1314             if (isDisposed) {
1315                 log.log(Level.FINE, "refresh() request for a disposed web page.");
1316                 return;
1317             }
1318             if (!frames.contains(frameID)) {
1319                 return;
1320             }
1321             twkRefresh(frameID);
1322 
1323         } finally {
1324             unlockPage();
1325         }
1326     }
1327 
1328     public BackForwardList createBackForwardList() {
1329         return new BackForwardList(this);
1330     }
1331 
1332     public boolean goBack() {
1333         lockPage();
1334         try {
1335             log.log(Level.FINE, "Go back");
1336             if (isDisposed) {
1337                 log.log(Level.FINE, "goBack() request for a disposed web page.");
1338                 return false;
1339             }
1340             return twkGoBackForward(getPage(), -1);
1341 
1342         } finally {
1343             unlockPage();
1344         }
1345     }
1346 
1347     public boolean goForward() {
1348         lockPage();
1349         try {
1350             log.log(Level.FINE, "Go forward");
1351             if (isDisposed) {
1352                 log.log(Level.FINE, "goForward() request for a disposed web page.");
1353                 return false;
1354             }
1355             return twkGoBackForward(getPage(), 1);
1356 
1357         } finally {
1358             unlockPage();
1359         }
1360     }
1361 
1362     public boolean copy() {
1363         lockPage();
1364         try {
1365             log.log(Level.FINE, "Copy");
1366             if (isDisposed) {
1367                 log.log(Level.FINE, "copy() request for a disposed web page.");
1368                 return false;
1369             }
1370             long frameID = getMainFrame();
1371             if (!frames.contains(frameID)) {
1372                 return false;
1373             }
1374             return twkCopy(frameID);
1375 
1376         } finally {
1377             unlockPage();
1378         }
1379     }
1380 
1381     // Find in page
1382     public boolean find(String stringToFind, boolean forward, boolean wrap, boolean matchCase) {
1383         lockPage();
1384         try {
1385             log.log(Level.FINE, "Find in page: stringToFind = " + stringToFind + ", " +
1386                     (forward ? "forward" : "backward") + (wrap ? ", wrap" : "") + (matchCase ? ", matchCase" : ""));
1387             if (isDisposed) {
1388                 log.log(Level.FINE, "find() request for a disposed web page.");
1389                 return false;
1390             }
1391             return twkFindInPage(getPage(), stringToFind, forward, wrap, matchCase);
1392 
1393         } finally {
1394             unlockPage();
1395         }
1396     }
1397 
1398     // Find in frame
1399     public boolean find(long frameID,
1400         String stringToFind, boolean forward, boolean wrap, boolean matchCase)
1401     {
1402         lockPage();
1403         try {
1404             log.log(Level.FINE, "Find in frame: stringToFind = " + stringToFind + ", " +
1405                     (forward ? "forward" : "backward") + (wrap ? ", wrap" : "") + (matchCase ? ", matchCase" : ""));
1406             if (isDisposed) {
1407                 log.log(Level.FINE, "find() request for a disposed web page.");
1408                 return false;
1409             }
1410             if (!frames.contains(frameID)) {
1411                 return false;
1412             }
1413             return twkFindInFrame(frameID, stringToFind, forward, wrap, matchCase);
1414 
1415         } finally {
1416             unlockPage();
1417         }
1418     }
1419 
1420     public void overridePreference(String key, String value) {
1421         lockPage();
1422         try {
1423             twkOverridePreference(getPage(), key, value);
1424         } finally {
1425             unlockPage();
1426         }
1427     }
1428 
1429     public void resetToConsistentStateBeforeTesting() {
1430         lockPage();
1431         try {
1432             twkResetToConsistentStateBeforeTesting(getPage());
1433         } finally {
1434             unlockPage();
1435         }
1436     }
1437 
1438     public float getZoomFactor(boolean textOnly) {
1439         lockPage();
1440         try {
1441             log.log(Level.FINE, "Get zoom factor, textOnly=" + textOnly);
1442             if (isDisposed) {
1443                 log.log(Level.FINE, "getZoomFactor() request for a disposed web page.");
1444                 return 1.0f;
1445             }
1446             long frameID = getMainFrame();
1447             if (!frames.contains(frameID)) {
1448                 return 1.0f;
1449             }
1450             return twkGetZoomFactor(frameID, textOnly);
1451         } finally {
1452             unlockPage();
1453         }
1454     }
1455 
1456     public void setZoomFactor(float zoomFactor, boolean textOnly) {
1457         lockPage();
1458         try {
1459             log.fine(String.format("Set zoom factor %.2f, textOnly=%b", zoomFactor, textOnly));
1460             if (isDisposed) {
1461                 log.log(Level.FINE, "setZoomFactor() request for a disposed web page.");
1462                 return;
1463             }
1464             long frameID = getMainFrame();
1465             if ((frameID == 0) || !frames.contains(frameID)) {
1466                 return;
1467             }
1468             twkSetZoomFactor(frameID, zoomFactor, textOnly);
1469         } finally {
1470             unlockPage();
1471         }
1472     }
1473 
1474     public void setFontSmoothingType(int fontSmoothingType) {
1475         this.fontSmoothingType = fontSmoothingType;
1476         repaintAll();
1477     }
1478 
1479     // DRT support
1480     public void reset(long frameID) {
1481         lockPage();
1482         try {
1483             log.log(Level.FINE, "Reset: frame = " + frameID);
1484             if (isDisposed) {
1485                 log.log(Level.FINE, "reset() request for a disposed web page.");
1486                 return;
1487             }
1488             if ((frameID == 0) || !frames.contains(frameID)) {
1489                 return;
1490             }
1491             twkReset(frameID);
1492 
1493         } finally {
1494             unlockPage();
1495         }
1496     }
1497 
1498     public Object executeScript(long frameID, String script) throws JSException {
1499         lockPage();
1500         try {
1501             log.log(Level.FINE, "execute script: \"" + script + "\" in frame = " + frameID);
1502             if (isDisposed) {
1503                 log.log(Level.FINE, "executeScript() request for a disposed web page.");
1504                 return null;
1505             }
1506             if ((frameID == 0) || !frames.contains(frameID)) {
1507                 return null;
1508             }
1509             return twkExecuteScript(frameID, script);
1510 
1511         } finally {
1512             unlockPage();
1513         }
1514     }
1515 
1516     public long getMainFrame() {
1517         lockPage();
1518         try {
1519             log.log(Level.FINER, "getMainFrame: page = " + pPage);
1520             if (isDisposed) {
1521                 log.log(Level.FINE, "getMainFrame() request for a disposed web page.");
1522                 return 0L;
1523             }
1524             long mainFrameID = twkGetMainFrame(getPage());
1525             log.log(Level.FINER, "Main frame = " + mainFrameID);
1526             frames.add(mainFrameID);
1527             return mainFrameID;
1528         } finally {
1529             unlockPage();
1530         }
1531     }
1532 
1533     public long getParentFrame(long childID) {
1534         lockPage();
1535         try {
1536             log.log(Level.FINE, "getParentFrame: child = " + childID);
1537             if (isDisposed) {
1538                 log.log(Level.FINE, "getParentFrame() request for a disposed web page.");
1539                 return 0L;
1540             }
1541             if (!frames.contains(childID)) {
1542                 return 0L;
1543             }
1544             return twkGetParentFrame(childID);
1545         } finally {
1546             unlockPage();
1547         }
1548     }
1549 
1550     public List<Long> getChildFrames(long parentID) {
1551         lockPage();
1552         try {
1553             log.log(Level.FINE, "getChildFrames: parent = " + parentID);
1554             if (isDisposed) {
1555                 log.log(Level.FINE, "getChildFrames() request for a disposed web page.");
1556                 return null;
1557             }
1558             if (!frames.contains(parentID)) {
1559                 return null;
1560             }
1561             long[] children = twkGetChildFrames(parentID);
1562             List<Long> childrenList = new LinkedList<Long>();
1563             for (long child : children) {
1564                 childrenList.add(Long.valueOf(child));
1565             }
1566             return childrenList;
1567         } finally {
1568             unlockPage();
1569         }
1570     }
1571 
1572     public WCRectangle getVisibleRect(long frameID) {
1573         lockPage();
1574         try {
1575             if (!frames.contains(frameID)) {
1576                 return null;
1577             }
1578             int[] arr = twkGetVisibleRect(frameID);
1579             if (arr != null) {
1580                 return new WCRectangle(arr[0], arr[1], arr[2], arr[3]);
1581             }
1582             return null;
1583         } finally {
1584             unlockPage();
1585         }
1586     }
1587 
1588     public void scrollToPosition(long frameID, WCPoint p) {
1589         lockPage();
1590         try {
1591             if (!frames.contains(frameID)) {
1592                 return;
1593             }
1594             twkScrollToPosition(frameID, p.getIntX(), p.getIntY());
1595         } finally {
1596             unlockPage();
1597         }
1598     }
1599 
1600     public WCSize getContentSize(long frameID) {
1601         lockPage();
1602         try {
1603             if (!frames.contains(frameID)) {
1604                 return null;
1605             }
1606             int[] arr = twkGetContentSize(frameID);
1607             if (arr != null) {
1608                 return new WCSize(arr[0], arr[1]);
1609             }
1610             return null;
1611         } finally {
1612             unlockPage();
1613         }
1614     }
1615 
1616     // ---- DOM ---- //
1617 
1618     public Document getDocument(long frameID) {
1619         lockPage();
1620         try {
1621             log.log(Level.FINE, "getDocument");
1622             if (isDisposed) {
1623                 log.log(Level.FINE, "getDocument() request for a disposed web page.");
1624                 return null;
1625             }
1626 
1627             if (!frames.contains(frameID)) {
1628                 return null;
1629             }
1630             return twkGetDocument(frameID);
1631         } finally {
1632             unlockPage();
1633         }
1634     }
1635 
1636     public Element getOwnerElement(long frameID) {
1637         lockPage();
1638         try {
1639             log.log(Level.FINE, "getOwnerElement");
1640             if (isDisposed) {
1641                 log.log(Level.FINE, "getOwnerElement() request for a disposed web page.");
1642                 return null;
1643             }
1644 
1645             if (!frames.contains(frameID)) {
1646                 return null;
1647             }
1648             return twkGetOwnerElement(frameID);
1649         } finally {
1650             unlockPage();
1651         }
1652     }
1653 
1654    // ---- EDITING SUPPORT ---- //
1655 
1656     public boolean executeCommand(String command, String value) {
1657         lockPage();
1658         try {
1659             if (log.isLoggable(Level.FINE)) {
1660                 log.log(Level.FINE, "command: [{0}], value: [{1}]",
1661                         new Object[] {command, value});
1662             }
1663             if (isDisposed) {
1664                 log.log(Level.FINE, "Web page is already disposed");
1665                 return false;
1666             }
1667 
1668             boolean result = twkExecuteCommand(getPage(), command, value);
1669 
1670             log.log(Level.FINE, "result: [{0}]", result);
1671             return result;
1672         } finally {
1673             unlockPage();
1674         }
1675     }
1676 
1677     public boolean queryCommandEnabled(String command) {
1678         lockPage();
1679         try {
1680             log.log(Level.FINE, "command: [{0}]", command);
1681             if (isDisposed) {
1682                 log.log(Level.FINE, "Web page is already disposed");
1683                 return false;
1684             }
1685 
1686             boolean result = twkQueryCommandEnabled(getPage(), command);
1687 
1688             log.log(Level.FINE, "result: [{0}]", result);
1689             return result;
1690         } finally {
1691             unlockPage();
1692         }
1693     }
1694 
1695     public boolean queryCommandState(String command) {
1696         lockPage();
1697         try {
1698             log.log(Level.FINE, "command: [{0}]", command);
1699             if (isDisposed) {
1700                 log.log(Level.FINE, "Web page is already disposed");
1701                 return false;
1702             }
1703 
1704             boolean result = twkQueryCommandState(getPage(), command);
1705 
1706             log.log(Level.FINE, "result: [{0}]", result);
1707             return result;
1708         } finally {
1709             unlockPage();
1710         }
1711     }
1712 
1713     public String queryCommandValue(String command) {
1714         lockPage();
1715         try {
1716             log.log(Level.FINE, "command: [{0}]", command);
1717             if (isDisposed) {
1718                 log.log(Level.FINE, "Web page is already disposed");
1719                 return null;
1720             }
1721 
1722             String result = twkQueryCommandValue(getPage(), command);
1723 
1724             log.log(Level.FINE, "result: [{0}]", result);
1725             return result;
1726         } finally {
1727             unlockPage();
1728         }
1729     }
1730 
1731     public boolean isEditable() {
1732         lockPage();
1733         try {
1734             log.log(Level.FINE, "isEditable");
1735             if (isDisposed) {
1736                 log.log(Level.FINE, "isEditable() request for a disposed web page.");
1737                 return false;
1738             }
1739 
1740             return twkIsEditable(getPage());
1741         } finally {
1742             unlockPage();
1743         }
1744     }
1745 
1746     public void setEditable(boolean editable) {
1747         lockPage();
1748         try {
1749             log.log(Level.FINE, "setEditable");
1750             if (isDisposed) {
1751                 log.log(Level.FINE, "setEditable() request for a disposed web page.");
1752                 return;
1753             }
1754 
1755             twkSetEditable(getPage(), editable);
1756         } finally {
1757             unlockPage();
1758         }
1759     }
1760 
1761     /**
1762      * @return HTML content of the frame,
1763      *         or null if frame document is absent or non-HTML.
1764      */
1765     public String getHtml(long frameID) {
1766         lockPage();
1767         try {
1768             log.log(Level.FINE, "getHtml");
1769             if (isDisposed) {
1770                 log.log(Level.FINE, "getHtml() request for a disposed web page.");
1771                 return null;
1772             }
1773             if (!frames.contains(frameID)) {
1774                 return null;
1775             }
1776             return twkGetHtml(frameID);
1777         } finally {
1778             unlockPage();
1779         }
1780     }
1781 
1782     // ---- PRINTING SUPPORT ---- //
1783 
1784     public int beginPrinting(float width, float height) {
1785         lockPage();
1786         try {
1787             if (isDisposed) {
1788                 log.warning("beginPrinting() called for a disposed web page.");
1789                 return 0;
1790             }
1791             AtomicReference<Integer> retVal = new AtomicReference<>(0);
1792             final CountDownLatch l = new CountDownLatch(1);
1793             Invoker.getInvoker().invokeOnEventThread(() -> {
1794                 try {
1795                     int nPages = twkBeginPrinting(getPage(), width, height);
1796                     retVal.set(nPages);
1797                 } finally {
1798                     l.countDown();
1799                 }
1800             });
1801 
1802             try {
1803                 l.await();
1804             } catch (InterruptedException e) {
1805                 throw new RuntimeException(e);
1806             }
1807             return retVal.get();
1808         } finally {
1809             unlockPage();
1810         }
1811     }
1812 
1813     public void endPrinting() {
1814         lockPage();
1815         try {
1816             if (isDisposed) {
1817                 log.warning("endPrinting() called for a disposed web page.");
1818                 return;
1819             }
1820             final CountDownLatch l = new CountDownLatch(1);
1821             Invoker.getInvoker().invokeOnEventThread(() -> {
1822                 try {
1823                     twkEndPrinting(getPage());
1824                 } finally {
1825                     l.countDown();
1826                 }
1827             });
1828 
1829             try {
1830                 l.await();
1831             } catch (InterruptedException e) {
1832                 throw new RuntimeException(e);
1833             }
1834         } finally {
1835             unlockPage();
1836         }
1837     }
1838 
1839     public void print(final WCGraphicsContext gc, final int pageNumber, final float width) {
1840         lockPage();
1841         try {
1842             if (isDisposed) {
1843                 log.warning("print() called for a disposed web page.");
1844                 return;
1845             }
1846             final WCRenderQueue rq = WCGraphicsManager.getGraphicsManager().
1847                     createRenderQueue(null, true);
1848             final CountDownLatch l = new CountDownLatch(1);
1849             Invoker.getInvoker().invokeOnEventThread(() -> {
1850                 try {
1851                     twkPrint(getPage(), rq, pageNumber, width);
1852                 } finally {
1853                     l.countDown();
1854                 }
1855             });
1856 
1857             try {
1858                 l.await();
1859             } catch (InterruptedException e) {
1860                 rq.dispose();
1861                 return;
1862             }
1863             rq.decode(gc);
1864         } finally {
1865             unlockPage();
1866         }
1867     }
1868 
1869     public int getPageHeight() {
1870         return getFrameHeight(getMainFrame());
1871     }
1872 
1873     public int getFrameHeight(long frameID) {
1874         lockPage();
1875         try {
1876             log.log(Level.FINE, "Get page height");
1877             if (isDisposed) {
1878                 log.log(Level.FINE, "getFrameHeight() request for a disposed web page.");
1879                 return 0;
1880             }
1881             if (!frames.contains(frameID)) {
1882                 return 0;
1883             }
1884             int height = twkGetFrameHeight(frameID);
1885             log.log(Level.FINE, "Height = " + height);
1886             return height;
1887         } finally {
1888             unlockPage();
1889         }
1890     }
1891 
1892     public float adjustFrameHeight(long frameID,
1893                                    float oldTop, float oldBottom, float bottomLimit)
1894     {
1895         lockPage();
1896         try {
1897             log.log(Level.FINE, "Adjust page height");
1898             if (isDisposed) {
1899                 log.log(Level.FINE, "adjustFrameHeight() request for a disposed web page.");
1900                 return 0;
1901             }
1902             if (!frames.contains(frameID)) {
1903                 return 0;
1904             }
1905             return twkAdjustFrameHeight(frameID, oldTop, oldBottom, bottomLimit);
1906         } finally {
1907             unlockPage();
1908         }
1909     }
1910 
1911     // ---- SETTINGS ---- //
1912 
1913     /**
1914      * Returns the usePageCache settings field.
1915      * @return {@code true} if this object uses the page cache,
1916      *         {@code false} otherwise.
1917      */
1918     public boolean getUsePageCache() {
1919         lockPage();
1920         try {
1921             return twkGetUsePageCache(getPage());
1922         } finally {
1923             unlockPage();
1924         }
1925     }
1926 
1927     /**
1928      * Sets the usePageCache settings field.
1929      * @param usePageCache {@code true} to use the page cache,
1930      *        {@code false} to not use the page cache.
1931      */
1932     public void setUsePageCache(boolean usePageCache) {
1933         lockPage();
1934         try {
1935             twkSetUsePageCache(getPage(), usePageCache);
1936         } finally {
1937             unlockPage();
1938         }
1939     }
1940 
1941     public boolean getDeveloperExtrasEnabled() {
1942         lockPage();
1943         try {
1944             boolean result = twkGetDeveloperExtrasEnabled(getPage());
1945             log.log(Level.FINE,
1946                     "Getting developerExtrasEnabled, result: [{0}]",
1947                     result);
1948             return result;
1949         } finally {
1950             unlockPage();
1951         }
1952     }
1953 
1954     public void setDeveloperExtrasEnabled(boolean enabled) {
1955         lockPage();
1956         try {
1957             log.log(Level.FINE,
1958                     "Setting developerExtrasEnabled, value: [{0}]",
1959                     enabled);
1960             twkSetDeveloperExtrasEnabled(getPage(), enabled);
1961         } finally {
1962             unlockPage();
1963         }
1964     }
1965 
1966     public boolean isJavaScriptEnabled() {
1967         lockPage();
1968         try {
1969             return twkIsJavaScriptEnabled(getPage());
1970         } finally {
1971             unlockPage();
1972         }
1973     }
1974 
1975     public void setJavaScriptEnabled(boolean enable) {
1976         lockPage();
1977         try {
1978             twkSetJavaScriptEnabled(getPage(), enable);
1979         } finally {
1980             unlockPage();
1981         }
1982     }
1983 
1984     public boolean isContextMenuEnabled() {
1985         lockPage();
1986         try {
1987             return twkIsContextMenuEnabled(getPage());
1988         } finally {
1989             unlockPage();
1990         }
1991     }
1992 
1993     public void setContextMenuEnabled(boolean enable) {
1994         lockPage();
1995         try {
1996             twkSetContextMenuEnabled(getPage(), enable);
1997         } finally {
1998             unlockPage();
1999         }
2000     }
2001 
2002     public void setUserStyleSheetLocation(String url) {
2003         lockPage();
2004         try {
2005             twkSetUserStyleSheetLocation(getPage(), url);
2006         } finally {
2007             unlockPage();
2008         }
2009     }
2010 
2011     public String getUserAgent() {
2012         lockPage();
2013         try {
2014             return twkGetUserAgent(getPage());
2015         } finally {
2016             unlockPage();
2017         }
2018     }
2019 
2020     public void setUserAgent(String userAgent) {
2021         lockPage();
2022         try {
2023             twkSetUserAgent(getPage(), userAgent);
2024         } finally {
2025             unlockPage();
2026         }
2027     }
2028 
2029     public void setLocalStorageDatabasePath(String path) {
2030         lockPage();
2031         try {
2032             twkSetLocalStorageDatabasePath(getPage(), path);
2033         } finally {
2034             unlockPage();
2035         }
2036     }
2037 
2038     public void setLocalStorageEnabled(boolean enabled) {
2039         lockPage();
2040         try {
2041             twkSetLocalStorageEnabled(getPage(), enabled);
2042         } finally {
2043             unlockPage();
2044         }
2045     }
2046 
2047     // ---- INSPECTOR SUPPORT ---- //
2048 
2049     public void connectInspectorFrontend() {
2050         lockPage();
2051         try {
2052             log.log(Level.FINE, "Connecting inspector frontend");
2053             twkConnectInspectorFrontend(getPage());
2054         } finally {
2055             unlockPage();
2056         }
2057     }
2058 
2059     public void disconnectInspectorFrontend() {
2060         lockPage();
2061         try {
2062             log.log(Level.FINE, "Disconnecting inspector frontend");
2063             twkDisconnectInspectorFrontend(getPage());
2064         } finally {
2065             unlockPage();
2066         }
2067     }
2068 
2069     public void dispatchInspectorMessageFromFrontend(String message) {
2070         lockPage();
2071         try {
2072             if (log.isLoggable(Level.FINE)) {
2073                 log.log(Level.FINE,
2074                         "Dispatching inspector message from frontend, "
2075                         + "message: [{0}]",
2076                         message);
2077             }
2078             twkDispatchInspectorMessageFromFrontend(getPage(), message);
2079         } finally {
2080             unlockPage();
2081         }
2082     }
2083 
2084     // *************************************************************************
2085     // Native callbacks
2086     // *************************************************************************
2087 
2088     private void fwkFrameCreated(long frameID) {
2089         log.log(Level.FINE, "Frame created: frame = " + frameID);
2090         if (frames.contains(frameID)) {
2091             log.log(Level.FINE, "Error in fwkFrameCreated: frame is already in frames");
2092             return;
2093         }
2094         frames.add(frameID);
2095     }
2096 
2097     private void fwkFrameDestroyed(long frameID) {
2098         log.log(Level.FINE, "Frame destroyed: frame = " + frameID);
2099         if (!frames.contains(frameID)) {
2100             log.log(Level.FINE, "Error in fwkFrameDestroyed: frame is not found in frames");
2101             return;
2102         }
2103         frames.remove(frameID);
2104     }
2105 
2106     private void fwkRepaint(int x, int y, int w, int h) {
2107         lockPage();
2108         try {
2109             if (paintLog.isLoggable(Level.FINEST)) {
2110                 paintLog.log(Level.FINEST, "x: {0}, y: {1}, w: {2}, h: {3}",
2111                         new Object[] {x, y, w, h});
2112             }
2113             addDirtyRect(new WCRectangle(x, y, w, h));
2114         } finally {
2115             unlockPage();
2116         }
2117     }
2118 
2119     private void fwkScroll(int x, int y, int w, int h, int deltaX, int deltaY) {
2120         if (paintLog.isLoggable(Level.FINEST)) {
2121             paintLog.finest("Scroll: " + x + " " + y + " " + w + " " + h + "  " + deltaX + " " + deltaY);
2122         }
2123         if (pageClient == null || !pageClient.isBackBufferSupported()) {
2124             paintLog.finest("blit scrolling is switched off");
2125             // TODO: check why we return void, not boolean (see ScrollView::m_canBlitOnScroll)
2126             return;
2127         }
2128         scroll(x, y, w, h, deltaX, deltaY);
2129     }
2130 
2131     private void fwkTransferFocus(boolean forward) {
2132         log.log(Level.FINER, "Transfer focus " + (forward ? "forward" : "backward"));
2133 
2134         if (pageClient != null) {
2135             pageClient.transferFocus(forward);
2136         }
2137     }
2138 
2139     private void fwkSetCursor(long id) {
2140         log.log(Level.FINER, "Set cursor: " + id);
2141 
2142         if (pageClient != null) {
2143             pageClient.setCursor(id);
2144         }
2145     }
2146 
2147     private void fwkSetFocus(boolean focus) {
2148         log.log(Level.FINER, "Set focus: " + (focus ? "true" : "false"));
2149 
2150         if (pageClient != null) {
2151             pageClient.setFocus(focus);
2152         }
2153     }
2154 
2155     private void fwkSetTooltip(String tooltip) {
2156         log.log(Level.FINER, "Set tooltip: " + tooltip);
2157 
2158         if (pageClient != null) {
2159             pageClient.setTooltip(tooltip);
2160         }
2161     }
2162 
2163     private void fwkPrint() {
2164         log.log(Level.FINER, "Print");
2165 
2166         if (uiClient != null) {
2167             uiClient.print();
2168         }
2169     }
2170 
2171     private void fwkSetRequestURL(long pFrame, int id, String url) {
2172         log.log(Level.FINER, "Set request URL: id = " + id + ", url = " + url);
2173 
2174         synchronized (requestURLs) {
2175             requestURLs.put(id, url);
2176         }
2177     }
2178 
2179     private void fwkRemoveRequestURL(long pFrame, int id) {
2180         log.log(Level.FINER, "Set request URL: id = " + id);
2181 
2182         synchronized (requestURLs) {
2183             requestURLs.remove(id);
2184             requestStarted.remove(id);
2185         }
2186     }
2187 
2188     private WebPage fwkCreateWindow(
2189             boolean menu, boolean status, boolean toolbar, boolean resizable) {
2190         log.log(Level.FINER, "Create window");
2191 
2192         if (uiClient != null) {
2193             return uiClient.createPage(menu, status, toolbar, resizable);
2194         }
2195         return null;
2196     }
2197 
2198     private void fwkShowWindow() {
2199         log.log(Level.FINER, "Show window");
2200 
2201         if (uiClient != null) {
2202             uiClient.showView();
2203         }
2204     }
2205 
2206     private void fwkCloseWindow() {
2207         log.log(Level.FINER, "Close window");
2208 
2209         if (permitCloseWindowAction()) {
2210             if (uiClient != null) {
2211                 uiClient.closePage();
2212             }
2213         }
2214     }
2215 
2216     private WCRectangle fwkGetWindowBounds() {
2217         log.log(Level.FINE, "Get window bounds");
2218 
2219         if (uiClient != null) {
2220             WCRectangle bounds = uiClient.getViewBounds();
2221             if (bounds != null) {
2222                 return bounds;
2223             }
2224         }
2225         return fwkGetPageBounds();
2226     }
2227 
2228     private void fwkSetWindowBounds(int x, int y, int w, int h) {
2229         log.log(Level.FINER, "Set window bounds: " + x + " " + y + " " + w + " " + h);
2230 
2231         if (uiClient != null) {
2232             uiClient.setViewBounds(new WCRectangle(x, y, w, h));
2233         }
2234     }
2235 
2236     private WCRectangle fwkGetPageBounds() {
2237         log.log(Level.FINER, "Get page bounds");
2238         return new WCRectangle(0, 0, width, height);
2239     }
2240 
2241     private void fwkSetScrollbarsVisible(boolean visible) {
2242         // TODO: handle this request internally
2243     }
2244 
2245     private void fwkSetStatusbarText(String text) {
2246         log.log(Level.FINER, "Set statusbar text: " + text);
2247 
2248         if (uiClient != null) {
2249             uiClient.setStatusbarText(text);
2250         }
2251     }
2252 
2253     private String[] fwkChooseFile(String initialFileName, boolean multiple, String mimeFilters) {
2254         log.log(Level.FINER, "Choose file, initial=" + initialFileName);
2255 
2256         return uiClient != null
2257                 ? uiClient.chooseFile(initialFileName, multiple, mimeFilters)
2258                 : null;
2259     }
2260 
2261     private void fwkStartDrag(
2262           Object image,
2263           int imageOffsetX, int imageOffsetY,
2264           int eventPosX, int eventPosY,
2265           String[] mimeTypes, Object[] values,
2266           boolean isImageSource, String imageFileExt)
2267     {
2268         log.log(Level.FINER, "Start drag: ");
2269         if (uiClient != null) {
2270             uiClient.startDrag(
2271                   WCImage.getImage(image),
2272                   imageOffsetX, imageOffsetY,
2273                   eventPosX, eventPosY,
2274                   mimeTypes, values,
2275                   isImageSource, imageFileExt);
2276         }
2277     }
2278 
2279     private WCPoint fwkScreenToWindow(WCPoint ptScreen) {
2280         log.log(Level.FINER, "fwkScreenToWindow");
2281 
2282         if (pageClient != null) {
2283             return pageClient.screenToWindow(ptScreen);
2284         }
2285         return ptScreen;
2286     }
2287 
2288     private WCPoint fwkWindowToScreen(WCPoint ptWindow) {
2289         log.log(Level.FINER, "fwkWindowToScreen");
2290 
2291         if (pageClient != null) {
2292             return pageClient.windowToScreen(ptWindow);
2293         }
2294         return ptWindow;
2295     }
2296 
2297 
2298     private void fwkAlert(String text) {
2299         log.log(Level.FINE, "JavaScript alert(): text = " + text);
2300 
2301         if (uiClient != null) {
2302             uiClient.alert(text);
2303         }
2304     }
2305 
2306     private boolean fwkConfirm(String text) {
2307         log.log(Level.FINE, "JavaScript confirm(): text = " + text);
2308 
2309         if (uiClient != null) {
2310             return uiClient.confirm(text);
2311         }
2312         return false;
2313     }
2314 
2315     private String fwkPrompt(String text, String defaultValue) {
2316         log.log(Level.FINE, "JavaScript prompt(): text = " + text + ", default = " + defaultValue);
2317 
2318         if (uiClient != null) {
2319             return uiClient.prompt(text, defaultValue);
2320         }
2321         return null;
2322     }
2323 
2324     private void fwkAddMessageToConsole(String message, int lineNumber,
2325             String sourceId)
2326     {
2327         log.log(Level.FINE, "fwkAddMessageToConsole(): message = " + message
2328                 + ", lineNumber = " + lineNumber + ", sourceId = " + sourceId);
2329         if (pageClient != null) {
2330             pageClient.addMessageToConsole(message, lineNumber, sourceId);
2331         }
2332     }
2333 
2334     private void fwkFireLoadEvent(long frameID, int state,
2335                                   String url, String contentType,
2336                                   double progress, int errorCode)
2337     {
2338         log.log(Level.FINER, "Load event: pFrame = " + frameID + ", state = " + state +
2339                 ", url = " + url + ", contenttype=" + contentType +
2340                 ", progress = " + progress + ", error = " + errorCode);
2341 
2342         fireLoadEvent(frameID, state, url, contentType, progress, errorCode);
2343     }
2344 
2345     private void fwkFireResourceLoadEvent(long frameID, int state,
2346                                           int id, String contentType,
2347                                           double progress, int errorCode)
2348     {
2349         log.log(Level.FINER, "Resource load event: pFrame = " + frameID + ", state = " + state +
2350                 ", id = " + id + ", contenttype=" + contentType +
2351                 ", progress = " + progress + ", error = " + errorCode);
2352 
2353         String url = requestURLs.get(id);
2354         if (url == null) {
2355             log.log(Level.FINE, "Error in fwkFireResourceLoadEvent: unknown request id " + id);
2356             return;
2357         }
2358 
2359         int eventState = state;
2360         // convert second and all subsequent STARTED into REDIRECTED
2361         if (state == LoadListenerClient.RESOURCE_STARTED) {
2362             if (requestStarted.contains(id)) {
2363                 eventState = LoadListenerClient.RESOURCE_REDIRECTED;
2364             } else {
2365                 requestStarted.add(id);
2366             }
2367         }
2368 
2369         fireResourceLoadEvent(frameID, eventState, url, contentType, progress, errorCode);
2370     }
2371 
2372     private boolean fwkPermitNavigateAction(long pFrame, String url) {
2373         log.log(Level.FINE, "Policy: permit NAVIGATE: pFrame = " + pFrame + ", url = " + url);
2374 
2375         if (policyClient != null) {
2376             return policyClient.permitNavigateAction(pFrame, str2url(url));
2377         }
2378         return true;
2379     }
2380 
2381     private boolean fwkPermitRedirectAction(long pFrame, String url) {
2382         log.log(Level.FINE, "Policy: permit REDIRECT: pFrame = " + pFrame + ", url = " + url);
2383 
2384         if (policyClient != null) {
2385             return policyClient.permitRedirectAction(pFrame, str2url(url));
2386         }
2387         return true;
2388     }
2389 
2390     private boolean fwkPermitAcceptResourceAction(long pFrame, String url) {
2391         log.log(Level.FINE, "Policy: permit ACCEPT_RESOURCE: pFrame + " + pFrame + ", url = " + url);
2392 
2393         if (policyClient != null) {
2394             return policyClient.permitAcceptResourceAction(pFrame, str2url(url));
2395         }
2396         return true;
2397     }
2398 
2399     private boolean fwkPermitSubmitDataAction(long pFrame, String url,
2400                                               String httpMethod, boolean isSubmit)
2401     {
2402         log.log(Level.FINE, "Policy: permit " + (isSubmit ? "" : "RE") + "SUBMIT_DATA: pFrame = " +
2403                 pFrame + ", url = " + url + ", httpMethod = " + httpMethod);
2404 
2405         if (policyClient != null) {
2406             if (isSubmit) {
2407                 return policyClient.permitSubmitDataAction(pFrame, str2url(url), httpMethod);
2408             } else {
2409                 return policyClient.permitResubmitDataAction(pFrame, str2url(url), httpMethod);
2410             }
2411         }
2412         return true;
2413     }
2414 
2415     private boolean fwkPermitEnableScriptsAction(long pFrame, String url) {
2416         log.log(Level.FINE, "Policy: permit ENABLE_SCRIPTS: pFrame + " + pFrame + ", url = " + url);
2417 
2418         if (policyClient != null) {
2419             return policyClient.permitEnableScriptsAction(pFrame, str2url(url));
2420         }
2421         return true;
2422     }
2423 
2424     private boolean fwkPermitNewWindowAction(long pFrame, String url) {
2425         log.log(Level.FINE, "Policy: permit NEW_PAGE: pFrame = " + pFrame + ", url = " + url);
2426 
2427         if (policyClient != null) {
2428             return policyClient.permitNewPageAction(pFrame, str2url(url));
2429         }
2430         return true;
2431     }
2432 
2433     // Called from fwkCloseWindow, that's why no "fwk" prefix
2434     private boolean permitCloseWindowAction() {
2435         log.log(Level.FINE, "Policy: permit CLOSE_PAGE");
2436 
2437         if (policyClient != null) {
2438             // Unfortunately, webkit doesn't provide an information about what
2439             // web frame initiated close window request, so using main frame here
2440             return policyClient.permitClosePageAction(getMainFrame());
2441         }
2442         return true;
2443     }
2444 
2445     private void fwkRepaintAll() {
2446         log.log(Level.FINE, "Repainting the entire page");
2447         repaintAll();
2448     }
2449 
2450     private boolean fwkSendInspectorMessageToFrontend(String message) {
2451         if (log.isLoggable(Level.FINE)) {
2452             log.log(Level.FINE,
2453                     "Sending inspector message to frontend, message: [{0}]",
2454                     message);
2455         }
2456         boolean result = false;
2457         if (inspectorClient != null) {
2458             log.log(Level.FINE, "Invoking inspector client");
2459             result = inspectorClient.sendMessageToFrontend(message);
2460         }
2461         if (log.isLoggable(Level.FINE)) {
2462             log.log(Level.FINE, "Result: [{0}]", result);
2463         }
2464         return result;
2465     }
2466 
2467     // ---- DumpRenderTree support ---- //
2468 
2469     public static int getWorkerThreadCount() {
2470         return twkWorkerThreadCount();
2471     }
2472 
2473     private static native int twkWorkerThreadCount();
2474 
2475     private void fwkDidClearWindowObject(long pContext, long pWindowObject) {
2476         if (pageClient != null) {
2477             pageClient.didClearWindowObject(pContext, pWindowObject);
2478         }
2479     }
2480 
2481     // *************************************************************************
2482     // Private methods
2483     // *************************************************************************
2484 
2485     private URL str2url(String url) {
2486         try {
2487             return newURL(url);
2488         } catch (MalformedURLException ex) {
2489             log.log(Level.FINE, "Exception while converting \"" + url + "\" to URL", ex);
2490         }
2491         return null;
2492     }
2493 
2494     private void fireLoadEvent(long frameID, int state, String url,
2495             String contentType, double progress, int errorCode)
2496     {
2497         for (LoadListenerClient l : loadListenerClients) {
2498             l.dispatchLoadEvent(frameID, state, url, contentType, progress, errorCode);
2499         }
2500     }
2501 
2502     private void fireResourceLoadEvent(long frameID, int state, String url,
2503             String contentType, double progress, int errorCode)
2504     {
2505         for (LoadListenerClient l : loadListenerClients) {
2506             l.dispatchResourceLoadEvent(frameID, state, url, contentType, progress, errorCode);
2507         }
2508     }
2509 
2510     private void repaintAll() {
2511         dirtyRects.clear();
2512         addDirtyRect(new WCRectangle(0, 0, width, height));
2513     }
2514 
2515     // Package scope method for testing
2516     int test_getFramesCount() {
2517         return frames.size();
2518     }
2519 
2520     // *************************************************************************
2521     // Native methods
2522     // *************************************************************************
2523 
2524     private static native void twkInitWebCore(boolean useJIT, boolean useDFGJIT);
2525     private native long twkCreatePage(boolean editable);
2526     private native void twkInit(long pPage, boolean usePlugins, float devicePixelScale);
2527     private native void twkDestroyPage(long pPage);
2528 
2529     private native long twkGetMainFrame(long pPage);
2530     private native long twkGetParentFrame(long pFrame);
2531     private native long[] twkGetChildFrames(long pFrame);
2532 
2533     private native String twkGetName(long pFrame);
2534     private native String twkGetURL(long pFrame);
2535     private native String twkGetInnerText(long pFrame);
2536     private native String twkGetRenderTree(long pFrame);
2537     private native String twkGetContentType(long pFrame);
2538     private native String twkGetTitle(long pFrame);
2539     private native String twkGetIconURL(long pFrame);
2540     private native static Document twkGetDocument(long pFrame);
2541     private native static Element twkGetOwnerElement(long pFrame);
2542 
2543     private native void twkOpen(long pFrame, String url);
2544     private native void twkOverridePreference(long pPage, String key, String value);
2545     private native void twkResetToConsistentStateBeforeTesting(long pPage);
2546     private native void twkLoad(long pFrame, String text, String contentType);
2547     private native boolean twkIsLoading(long pFrame);
2548     private native void twkStop(long pFrame);
2549     private native void twkStopAll(long pPage); // sync
2550     private native void twkRefresh(long pFrame);
2551 
2552     private native boolean twkGoBackForward(long pPage, int distance);
2553 
2554     private native boolean twkCopy(long pFrame);
2555     private native boolean twkFindInPage(long pPage,
2556                                          String stringToFind, boolean forward,
2557                                          boolean wrap, boolean matchCase);
2558     private native boolean twkFindInFrame(long pFrame,
2559                                           String stringToFind, boolean forward,
2560                                           boolean wrap, boolean matchCase);
2561 
2562     private native float twkGetZoomFactor(long pFrame, boolean textOnly);
2563     private native void twkSetZoomFactor(long pFrame, float zoomFactor, boolean textOnly);
2564 
2565     private native Object twkExecuteScript(long pFrame, String script);
2566 
2567     private native void twkReset(long pFrame);
2568 
2569     private native int twkGetFrameHeight(long pFrame);
2570     private native int twkBeginPrinting(long pPage, float width, float height);
2571     private native void twkEndPrinting(long pPage);
2572     private native void twkPrint(long pPage, WCRenderQueue gc, int pageNumber, float width);
2573     private native float twkAdjustFrameHeight(long pFrame, float oldTop, float oldBottom, float bottomLimit);
2574 
2575     private native int[] twkGetVisibleRect(long pFrame);
2576     private native void twkScrollToPosition(long pFrame, int x, int y);
2577     private native int[] twkGetContentSize(long pFrame);
2578     private native void twkSetTransparent(long pFrame, boolean isTransparent);
2579     private native void twkSetBackgroundColor(long pFrame, int backgroundColor);
2580 
2581     private native void twkSetBounds(long pPage, int x, int y, int w, int h);
2582     private native void twkPrePaint(long pPage);
2583     private native void twkUpdateContent(long pPage, WCRenderQueue rq, int x, int y, int w, int h);
2584     private native void twkPostPaint(long pPage, WCRenderQueue rq,
2585                                      int x, int y, int w, int h);
2586 
2587     private native String twkGetEncoding(long pPage);
2588     private native void twkSetEncoding(long pPage, String encoding);
2589 
2590     private native void twkProcessFocusEvent(long pPage, int id, int direction);
2591     private native boolean twkProcessKeyEvent(long pPage, int type, String text,
2592                                               String keyIdentifier,
2593                                               int windowsVirtualKeyCode,
2594                                               boolean shift, boolean ctrl,
2595                                               boolean alt, boolean meta, double when);
2596     private native boolean twkProcessMouseEvent(long pPage, int id,
2597                                                 int button, int clickCount,
2598                                                 int x, int y, int sx, int sy,
2599                                                 boolean shift, boolean control, boolean alt, boolean meta,
2600                                                 boolean popupTrigger, double when);
2601     private native boolean twkProcessMouseWheelEvent(long pPage,
2602                                                      int x, int y, int sx, int sy,
2603                                                      float dx, float dy,
2604                                                      boolean shift, boolean control, boolean alt, boolean meta,
2605                                                      double when);
2606     private native boolean twkProcessInputTextChange(long pPage, String committed, String composed,
2607                                                      int[] attributes, int caretPosition);
2608     private native boolean twkProcessCaretPositionChange(long pPage, int caretPosition);
2609     private native int[] twkGetTextLocation(long pPage, int charIndex);
2610     private native int twkGetInsertPositionOffset(long pPage);
2611     private native int twkGetCommittedTextLength(long pPage);
2612     private native String twkGetCommittedText(long pPage);
2613     private native String twkGetSelectedText(long pPage);
2614 
2615     private native int twkProcessDrag(long page,
2616             int commandId,
2617             String[] mimeTypes, String[] values,
2618             int x, int y,
2619             int screenX, int screenY,
2620             int dndActionId);
2621 
2622     private native boolean twkExecuteCommand(long page, String command,
2623                                              String value);
2624     private native boolean twkQueryCommandEnabled(long page, String command);
2625     private native boolean twkQueryCommandState(long page, String command);
2626     private native String twkQueryCommandValue(long page, String command);
2627     private native boolean twkIsEditable(long page);
2628     private native void twkSetEditable(long page, boolean editable);
2629     private native String twkGetHtml(long pFrame);
2630 
2631     private native boolean twkGetUsePageCache(long page);
2632     private native void twkSetUsePageCache(long page, boolean usePageCache);
2633     private native boolean twkGetDeveloperExtrasEnabled(long page);
2634     private native void twkSetDeveloperExtrasEnabled(long page,
2635                                                      boolean enabled);
2636     private native boolean twkIsJavaScriptEnabled(long page);
2637     private native void twkSetJavaScriptEnabled(long page, boolean enable);
2638     private native boolean twkIsContextMenuEnabled(long page);
2639     private native void twkSetContextMenuEnabled(long page, boolean enable);
2640     private native void twkSetUserStyleSheetLocation(long page, String url);
2641     private native String twkGetUserAgent(long page);
2642     private native void twkSetUserAgent(long page, String userAgent);
2643     private native void twkSetLocalStorageDatabasePath(long page, String path);
2644     private native void twkSetLocalStorageEnabled(long page, boolean enabled);
2645 
2646     private native int twkGetUnloadEventListenersCount(long pFrame);
2647 
2648     private native void twkConnectInspectorFrontend(long pPage);
2649     private native void twkDisconnectInspectorFrontend(long pPage);
2650     private native void twkDispatchInspectorMessageFromFrontend(long pPage,
2651                                                                 String message);
2652     private static native void twkDoJSCGarbageCollection();
2653 }