1 /*******************************************************************************
2 * Copyright (c) 2003, 2017 IBM Corporation and others.
3 *
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
8 *
9 * SPDX-License-Identifier: EPL-2.0
10 *
11 * Contributors:
12 * IBM Corporation - initial API and implementation
13 *******************************************************************************/
14 package org.eclipse.swt.browser;
15
16 import java.io.*;
17 import java.net.*;
18 import java.util.*;
19
20 import org.eclipse.swt.*;
21 import org.eclipse.swt.graphics.*;
22 import org.eclipse.swt.internal.*;
23 import org.eclipse.swt.internal.ole.win32.*;
24 import org.eclipse.swt.internal.win32.*;
25 import org.eclipse.swt.ole.win32.*;
26 import org.eclipse.swt.widgets.*;
27
28 class IE extends WebBrowser {
29
30 OleFrame frame;
31 WebSite site;
32 OleAutomation auto;
33 OleListener domListener;
34 OleAutomation[] documents = new OleAutomation[0];
35
36 boolean back, forward, delaySetText, ignoreDispose, ignoreTraverse, performingInitialNavigate;
37 boolean installFunctionsOnDocumentComplete, untrustedText, isRefresh, isAboutBlank;
38 Point location;
39 Point size;
40 boolean addressBar = true, menuBar = true, statusBar = true, toolBar = true;
41 long globalDispatch;
42 String html, lastNavigateURL, uncRedirect;
43 Object[] pendingText, pendingUrl;
44 int style, lastKeyCode, lastCharCode;
45 int lastMouseMoveX, lastMouseMoveY;
46
47 static boolean Initialized;
48 static int IEVersion, PDFCount;
49 static String ProgId = "Shell.Explorer"; //$NON-NLS-1$
50
51 static final int BeforeNavigate2 = 0xfa;
52 static final int CommandStateChange = 0x69;
53 static final int DocumentComplete = 0x103;
54 static final int DownloadComplete = 0x68;
55 static final int NavigateComplete2 = 0xfc;
56 static final int NewWindow2 = 0xfb;
57 static final int OnMenuBar = 0x100;
58 static final int OnStatusBar = 0x101;
59 static final int OnToolBar = 0xff;
60 static final int OnVisible = 0xfe;
61 static final int ProgressChange = 0x6c;
62 static final int RegisterAsBrowser = 0x228;
63 static final int StatusTextChange = 0x66;
64 static final int TitleChange = 0x71;
65 static final int WindowClosing = 0x107;
66 static final int WindowSetHeight = 0x10b;
67 static final int WindowSetLeft = 0x108;
68 static final int WindowSetResizable = 0x106;
69 static final int WindowSetTop = 0x109;
70 static final int WindowSetWidth = 0x10a;
71 static final int NavigateError = 0x10f;
72
73 static final short CSC_NAVIGATEFORWARD = 1;
74 static final short CSC_NAVIGATEBACK = 2;
75 static final int INET_E_DEFAULT_ACTION = 0x800C0011;
76 static final int INET_E_RESOURCE_NOT_FOUND = 0x800C0005;
77 static final int READYSTATE_COMPLETE = 4;
78 static final int URLPOLICY_ALLOW = 0x00;
79 static final int URLPOLICY_DISALLOW = 0x03;
80 static final int URLPOLICY_JAVA_PROHIBIT = 0x0;
81 static final int URLPOLICY_JAVA_LOW = 0x00030000;
82 static final int URLZONE_LOCAL_MACHINE = 0;
83 static final int URLZONE_INTRANET = 1;
84 static final int URLACTION_ACTIVEX_MIN = 0x00001200;
85 static final int URLACTION_ACTIVEX_MAX = 0x000013ff;
86 static final int URLACTION_ACTIVEX_RUN = 0x00001200;
87 static final int URLACTION_FEATURE_ZONE_ELEVATION = 0x00002101;
88 static final int URLACTION_JAVA_MIN = 0x00001C00;
89 static final int URLACTION_JAVA_MAX = 0x00001Cff;
90 static final int URLACTION_SCRIPT_RUN = 0x00001400;
91
92 static final int DISPID_AMBIENT_DLCONTROL = -5512;
93 static final int DLCTL_DLIMAGES = 0x00000010;
94 static final int DLCTL_VIDEOS = 0x00000020;
95 static final int DLCTL_BGSOUNDS = 0x00000040;
96 static final int DLCTL_NO_SCRIPTS = 0x00000080;
97 static final int DLCTL_NO_JAVA = 0x00000100;
98 static final int DLCTL_NO_RUNACTIVEXCTLS = 0x00000200;
99 static final int DLCTL_NO_DLACTIVEXCTLS = 0x00000400;
100 static final int DLCTL_DOWNLOADONLY = 0x00000800;
101 static final int DLCTL_NO_FRAMEDOWNLOAD = 0x00001000;
102 static final int DLCTL_RESYNCHRONIZE = 0x00002000;
103 static final int DLCTL_PRAGMA_NO_CACHE = 0x00004000;
104 static final int DLCTL_FORCEOFFLINE = 0x10000000;
105 static final int DLCTL_NO_CLIENTPULL = 0x20000000;
106 static final int DLCTL_SILENT = 0x40000000;
107 static final int DOCHOSTUIFLAG_THEME = 0x00040000;
108 static final int DOCHOSTUIFLAG_NO3DBORDER = 0x0000004;
109 static final int DOCHOSTUIFLAG_NO3DOUTERBORDER = 0x00200000;
110 static final int DOCHOSTUIFLAG_ENABLE_REDIRECT_NOTIFICATION = 0x04000000;
111 static final int DOCHOSTUIFLAG_DPI_AWARE = 0x40000000;
112
113 static final String ABOUT_BLANK = "about:blank"; //$NON-NLS-1$
114 static final String CLSID_SHELLEXPLORER1 = "{EAB22AC3-30C1-11CF-A7EB-0000C05BAE0B}"; //$NON-NLS-1$
115 static final int DEFAULT_IE_VERSION = 9999;
116 static final String EXTENSION_PDF = ".pdf"; //$NON-NLS-1$
117 static final String HTML_DOCUMENT = "HTML Document"; //$NON-NLS-1$
118 static final int MAX_PDF = 20;
119 static final char SEPARATOR_OS = File.separatorChar;
120 static final String PROPERTY_IEVERSION = "org.eclipse.swt.browser.IEVersion"; //$NON-NLS-1$
121 static final String VALUE_DEFAULT = "default"; //$NON-NLS-1$
122
123 static final String EVENT_DOUBLECLICK = "dblclick"; //$NON-NLS-1$
124 static final String EVENT_DRAGEND = "dragend"; //$NON-NLS-1$
125 static final String EVENT_DRAGSTART = "dragstart"; //$NON-NLS-1$
126 static final String EVENT_KEYDOWN = "keydown"; //$NON-NLS-1$
127 static final String EVENT_KEYPRESS = "keypress"; //$NON-NLS-1$
128 static final String EVENT_KEYUP = "keyup"; //$NON-NLS-1$
129 static final String EVENT_MOUSEMOVE = "mousemove"; //$NON-NLS-1$
130 static final String EVENT_MOUSEWHEEL = "mousewheel"; //$NON-NLS-1$
131 static final String EVENT_MOUSEUP = "mouseup"; //$NON-NLS-1$
132 static final String EVENT_MOUSEDOWN = "mousedown"; //$NON-NLS-1$
133 static final String EVENT_MOUSEOUT = "mouseout"; //$NON-NLS-1$
134 static final String EVENT_MOUSEOVER = "mouseover"; //$NON-NLS-1$
135 static final String PROTOCOL_FILE = "file://"; //$NON-NLS-1$
136 static final String PROPERTY_ALTKEY = "altKey"; //$NON-NLS-1$
137 static final String PROPERTY_BUTTON = "button"; //$NON-NLS-1$
138 static final String PROPERTY_CTRLKEY = "ctrlKey"; //$NON-NLS-1$
139 static final String PROPERTY_DOCUMENT = "Document"; //$NON-NLS-1$
140 static final String PROPERTY_FROMELEMENT = "fromElement"; //$NON-NLS-1$
141 static final String PROPERTY_KEYCODE = "keyCode"; //$NON-NLS-1$
142 static final String PROPERTY_REPEAT = "repeat"; //$NON-NLS-1$
143 static final String PROPERTY_RETURNVALUE = "returnValue"; //$NON-NLS-1$
144 static final String PROPERTY_SCREENX = "screenX"; //$NON-NLS-1$
145 static final String PROPERTY_SCREENY = "screenY"; //$NON-NLS-1$
146 static final String PROPERTY_SHIFTKEY = "shiftKey"; //$NON-NLS-1$
147 static final String PROPERTY_TOELEMENT = "toElement"; //$NON-NLS-1$
148 static final String PROPERTY_TYPE = "type"; //$NON-NLS-1$
149 static final String PROPERTY_WHEELDELTA = "wheelDelta"; //$NON-NLS-1$
150
151 static {
152 NativeClearSessions = () -> {
153 OS.InternetSetOption (0, OS.INTERNET_OPTION_END_BROWSER_SESSION, 0, 0);
154 };
155
156 NativeGetCookie = () -> {
157 TCHAR url = new TCHAR (0, CookieUrl, true);
158 TCHAR cookieData = new TCHAR (0, 8192);
159 int[] size = new int[] {cookieData.length ()};
160 if (!OS.InternetGetCookie (url, null, cookieData, size)) {
161 /* original cookieData size was not large enough */
162 size[0] /= TCHAR.sizeof;
163 cookieData = new TCHAR (0, size[0]);
164 if (!OS.InternetGetCookie (url, null, cookieData, size)) return;
165 }
166 String allCookies = cookieData.toString (0, size[0]);
167 StringTokenizer tokenizer = new StringTokenizer (allCookies, ";"); //$NON-NLS-1$
168 while (tokenizer.hasMoreTokens ()) {
169 String cookie = tokenizer.nextToken ();
170 int index = cookie.indexOf ('=');
171 if (index != -1) {
172 String name = cookie.substring (0, index).trim ();
173 if (name.equals (CookieName)) {
174 CookieValue = cookie.substring (index + 1).trim ();
175 return;
176 }
177 }
178 }
179 };
180
181 NativeSetCookie = () -> {
182 TCHAR url = new TCHAR (0, CookieUrl, true);
183 TCHAR value = new TCHAR (0, CookieValue, true);
184 CookieResult = OS.InternetSetCookie (url, null, value);
185 };
186
187 /*
188 * The installed version of IE can be determined by looking at registry entry
189 * HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\svcVersion, or
190 * HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Version (for
191 * IE releases prior to IE10). Check this value in order to determine
192 * version-specific features that can be enabled.
193 */
194 TCHAR key = new TCHAR (0, "Software\\Microsoft\\Internet Explorer", true); //$NON-NLS-1$
195 long [] phkResult = new long [1];
196 if (OS.RegOpenKeyEx (OS.HKEY_LOCAL_MACHINE, key, 0, OS.KEY_READ, phkResult) == 0) {
197 int [] lpcbData = new int [1];
198 TCHAR buffer = new TCHAR (0, "svcVersion", true); //$NON-NLS-1$
199 int result = OS.RegQueryValueEx (phkResult [0], buffer, 0, null, (TCHAR) null, lpcbData);
200 if (result != 0) {
201 buffer = new TCHAR (0, "Version", true); //$NON-NLS-1$
202 result = OS.RegQueryValueEx (phkResult [0], buffer, 0, null, (TCHAR) null, lpcbData);
203 }
204 if (result == 0) {
205 TCHAR lpData = new TCHAR (0, lpcbData [0] / TCHAR.sizeof);
206 result = OS.RegQueryValueEx (phkResult [0], buffer, 0, null, lpData, lpcbData);
207 if (result == 0) {
208 String versionString = lpData.toString (0, lpData.strlen ());
209 int index = versionString.indexOf ("."); //$NON-NLS-1$
210 if (index != -1) {
211 String majorString = versionString.substring (0, index);
212 try {
213 IEVersion = Integer.valueOf (majorString).intValue ();
214 } catch (NumberFormatException e) {
215 /* just continue, version-specific features will not be enabled */
216 }
217 }
218 }
219 }
220 OS.RegCloseKey (phkResult [0]);
221 }
222
223 /*
224 * Registry entry HKEY_CLASSES_ROOT\Shell.Explorer\CLSID indicates which version of
225 * Shell.Explorer to use by default. We usually want to use this value because it
226 * typically points at the newest one that is available. However it is possible for
227 * this registry entry to be changed by another application to point at some other
228 * Shell.Explorer version.
229 *
230 * The Browser depends on the Shell.Explorer version being at least Shell.Explorer.2.
231 * If it is detected in the registry to be Shell.Explorer.1 then change the progId that
232 * will be embedded to explicitly specify Shell.Explorer.2.
233 */
234 key = new TCHAR (0, "Shell.Explorer\\CLSID", true); //$NON-NLS-1$
235 phkResult = new long [1];
236 if (OS.RegOpenKeyEx (OS.HKEY_CLASSES_ROOT, key, 0, OS.KEY_READ, phkResult) == 0) {
237 int [] lpcbData = new int [1];
238 int result = OS.RegQueryValueEx (phkResult [0], null, 0, null, (TCHAR) null, lpcbData);
239 if (result == 0) {
240 TCHAR lpData = new TCHAR (0, lpcbData [0] / TCHAR.sizeof);
241 result = OS.RegQueryValueEx (phkResult [0], null, 0, null, lpData, lpcbData);
242 if (result == 0) {
243 String clsid = lpData.toString (0, lpData.strlen ());
244 if (clsid.equals (CLSID_SHELLEXPLORER1)) {
245 /* Shell.Explorer.1 is the default, ensure that Shell.Explorer.2 is available */
246 key = new TCHAR (0, "Shell.Explorer.2", true); //$NON-NLS-1$
247 long [] phkResult2 = new long [1];
248 if (OS.RegOpenKeyEx (OS.HKEY_CLASSES_ROOT, key, 0, OS.KEY_READ, phkResult2) == 0) {
249 /* specify that Shell.Explorer.2 is to be used */
250 OS.RegCloseKey (phkResult2 [0]);
251 ProgId = "Shell.Explorer.2"; //$NON-NLS-1$
252 }
253 }
254 }
255 }
256 OS.RegCloseKey (phkResult [0]);
257 }
258
259 if (NativePendingCookies != null) {
260 SetPendingCookies (NativePendingCookies);
261 }
262 NativePendingCookies = null;
263 }
264
265 @Override
create(Composite parent, int style)266 public void create(Composite parent, int style) {
267 this.style = style;
268 frame = new OleFrame(browser, SWT.NONE);
269
270 try {
271 site = new WebSite(frame, SWT.NONE, ProgId);
272 } catch (SWTException e) {
273 browser.dispose();
274 SWT.error(SWT.ERROR_NO_HANDLES);
275 }
276
277 if (!Initialized) {
278 Initialized = true;
279 int version = 0;
280 String versionProperty = System.getProperty(PROPERTY_IEVERSION);
281 if (versionProperty != null) {
282 if (versionProperty.equalsIgnoreCase(VALUE_DEFAULT)) {
283 version = -1;
284 } else {
285 try {
286 version = Integer.valueOf(versionProperty).intValue();
287 } catch (NumberFormatException e) {
288 /*
289 * An invalid value was specified for the IEVersion java property. Ignore it
290 * and continue with the usual steps for determining the version to specify.
291 */
292 }
293 }
294 }
295 if (version == 0) {
296 if (IEVersion != 0) {
297 /*
298 * By default in Embedded IE the docuemntMode is Quirks(5)
299 * mode unless !DOCTYPE directives is defined in the HTML.
300 * As per MSDN IE8 and onwards, there is a way we could hint
301 * embedded IE to use current documentMode via appropriate
302 * version value in the registry. Refer bug 342145.
303 *
304 * Complete list of IE emulation modes is listed on MSDN:
305 * http://msdn.microsoft
306 * .com/en-us/library/ie/ee330730%28v=vs
307 * .85%29.aspx#browser_emulation
308 */
309 if (IEVersion >= 10) {
310 version = IEVersion * 1000 + 1;
311 }
312 else if (IEVersion >= 8) {
313 version = IEVersion * 1111;
314 }
315 else {
316 version = IEVersion * 1000;
317 }
318 } else {
319 version = DEFAULT_IE_VERSION;
320 }
321 }
322
323 if (version != -1) {
324 long[] key = new long[1];
325 final TCHAR subkey = new TCHAR(0, "Software\\Microsoft\\Internet Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION", true); //$NON-NLS-1$
326 if (OS.RegCreateKeyEx(OS.HKEY_CURRENT_USER, subkey, 0, null, OS.REG_OPTION_VOLATILE, OS.KEY_WRITE | OS.KEY_QUERY_VALUE, 0, key, null) == 0) {
327 TCHAR lpszFile = new TCHAR(0, OS.MAX_PATH);
328 OS.GetModuleFileName(0, lpszFile, lpszFile.length());
329 String path = lpszFile.toString(0, lpszFile.strlen());
330 int index = path.lastIndexOf(SEPARATOR_OS);
331 String executable = index != -1 ? path.substring(index + 1) : path;
332 final TCHAR lpValueName = new TCHAR(0, executable, true);
333 /*
334 * Program name & IE version entry is added to the Windows
335 * registry and same gets deleted during the dispose cycle.
336 * There is a possibility if the SWT application crashes or
337 * is exited forcefully, which leaves the registry entry
338 * as-is and hence if entry exists, updating it next time
339 * the SWT application creates embedded IE, refer bug 440300
340 */
341 int result = OS.RegQueryValueEx(key[0], lpValueName, 0, null, (int[])null, null);
342 if (result == 0 || result == OS.ERROR_FILE_NOT_FOUND) {
343 if (OS.RegSetValueEx(key[0], lpValueName, 0, OS.REG_DWORD, new int[] {version}, 4) == 0) {
344 parent.getDisplay().addListener(SWT.Dispose, event -> {
345 long[] key1 = new long[1];
346 if (OS.RegOpenKeyEx(OS.HKEY_CURRENT_USER, subkey, 0, OS.KEY_WRITE, key1) == 0) {
347 OS.RegDeleteValue(key1[0], lpValueName);
348 }
349 });
350 }
351 }
352 OS.RegCloseKey(key[0]);
353 }
354 }
355 }
356
357 site.doVerb(OLE.OLEIVERB_INPLACEACTIVATE);
358 auto = new OleAutomation(site);
359
360 domListener = e -> handleDOMEvent(e);
361
362 Listener listener = e -> {
363 switch (e.type) {
364 case SWT.Dispose: {
365 /* make this handler run after other dispose listeners */
366 if (ignoreDispose) {
367 ignoreDispose = false;
368 break;
369 }
370 ignoreDispose = true;
371 browser.notifyListeners (e.type, e);
372 e.type = SWT.NONE;
373
374 /* invoke onbeforeunload handlers */
375 if (!browser.isClosing) {
376 LocationListener[] oldLocationListeners = locationListeners;
377 locationListeners = new LocationListener[0];
378 site.ignoreAllMessages = true;
379 execute ("window.location.href='about:blank'"); //$NON-NLS-1$
380 site.ignoreAllMessages = false;
381 locationListeners = oldLocationListeners;
382 }
383
384 /*
385 * It is possible for the Browser's OLE frame to have been disposed
386 * by a Dispose listener that was invoked by notifyListeners above,
387 * so check for this before unhooking its DOM listeners.
388 */
389 if (!frame.isDisposed ()) unhookDOMListeners(documents);
390
391 for (OleAutomation document : documents) {
392 document.dispose();
393 }
394 documents = null;
395
396 Iterator<BrowserFunction> elements = functions.values().iterator ();
397 while (elements.hasNext ()) {
398 elements.next ().dispose (false);
399 }
400 functions = null;
401
402 lastNavigateURL = uncRedirect = null;
403 domListener = null;
404 if (auto != null) auto.dispose();
405 auto = null;
406 break;
407 }
408 case SWT.Resize: {
409 frame.setBounds(browser.getClientArea());
410 break;
411 }
412 case SWT.MouseWheel: {
413 /* MouseWheel events come from the DOM */
414 e.doit = false;
415 break;
416 }
417 case SWT.FocusIn: {
418 site.setFocus();
419 break;
420 }
421 case SWT.Traverse: {
422 /*
423 * Tabbing out of the browser can fail as a result of the WebSite
424 * control embedded within the Browser. The workaround is to
425 * listen for traversals and re-perform the traversal on the
426 * appropriate control.
427 */
428 if (e.detail == SWT.TRAVERSE_TAB_PREVIOUS && e.widget instanceof WebSite) {
429 /* otherwise will traverse to the Browser control */
430 browser.traverse(SWT.TRAVERSE_TAB_PREVIOUS, e);
431 e.doit = false;
432 }
433 /*
434 * Return traversals can sometimes come through TranslateAccelerator,
435 * depending on where focus is within the Browser. Traversal
436 * events should always be triggered by a key event from the DOM,
437 * so if a Traversal from TranslateAccelerator is detected
438 * (e.doit == true) then stop its propagation.
439 */
440 if (e.detail == SWT.TRAVERSE_RETURN && e.doit && e.widget instanceof Browser) {
441 e.type = SWT.None;
442 e.doit = false;
443 }
444 break;
445 }
446 }
447 };
448 browser.addListener(SWT.Dispose, listener);
449 browser.addListener(SWT.FocusIn, listener);
450 browser.addListener(SWT.Resize, listener);
451 browser.addListener(SWT.Traverse, listener);
452 site.addListener(SWT.MouseWheel, listener);
453 site.addListener(SWT.Traverse, listener);
454
455 OleListener oleListener = event -> {
456 /* callbacks are asynchronous, auto could be disposed */
457 if (auto != null) {
458 switch (event.type) {
459 case BeforeNavigate2: {
460
461 /* don't send client events if the initial navigate to about:blank has not completed */
462 if (performingInitialNavigate) break;
463
464 Variant varResult1 = event.arguments[1];
465 String url1 = varResult1.getString();
466
467 if (uncRedirect != null) {
468 /*
469 * Silently allow the navigate to proceed if the url is the first segment of a
470 * UNC path being navigated to (initiated by the NavigateError listener to show
471 * a name/password prompter), or if the url is the full UNC path (initiated by
472 * the NavigateComplete listener to redirect from the UNC's first segment to its
473 * full path).
474 */
475 if (uncRedirect.equals(url1) || (uncRedirect.startsWith(url1) && uncRedirect.indexOf('\\', 2) == url1.length())) {
476 Variant cancel1 = event.arguments[6];
477 if (cancel1 != null) {
478 long pCancel1 = cancel1.getByRef();
479 OS.MoveMemory(pCancel1, new short[] {OS.VARIANT_FALSE}, 2);
480 }
481 setAboutBlank(false);
482 break;
483 } else {
484 /*
485 * This navigate does not correspond to the previously-initiated
486 * UNC navigation so clear this state since it's no longer valid.
487 */
488 uncRedirect = null;
489 }
490 }
491
492 /*
493 * Feature in IE. For navigations on the local machine, BeforeNavigate2's url
494 * field contains a string representation of the file path in a non-URL format.
495 * In order to be consistent with the other Browser implementations, this
496 * case is detected and the string is changed to be a proper url string.
497 */
498 if (url1.indexOf(":/") == -1 && url1.indexOf(":\\") != -1) { //$NON-NLS-1$ //$NON-NLS-2$
499 TCHAR filePath1 = new TCHAR(0, url1, true);
500 TCHAR urlResult1 = new TCHAR(0, OS.INTERNET_MAX_URL_LENGTH);
501 int[] size1 = new int[] {urlResult1.length()};
502 if (OS.UrlCreateFromPath(filePath1, urlResult1, size1, 0) == COM.S_OK) {
503 url1 = urlResult1.toString(0, size1[0]);
504 } else {
505 url1 = PROTOCOL_FILE + url1.replace('\\', '/');
506 }
507 }
508
509 /* Disallow local file system accesses if the browser content is untrusted */
510 if (url1.startsWith(PROTOCOL_FILE) && _getUrl().startsWith(ABOUT_BLANK) && untrustedText) {
511 Variant cancel2 = event.arguments[6];
512 if (cancel2 != null) {
513 long pCancel2 = cancel2.getByRef();
514 OS.MoveMemory(pCancel2, new short[] {OS.VARIANT_TRUE}, 2);
515 }
516 break;
517 }
518
519 LocationEvent newEvent1 = new LocationEvent(browser);
520 newEvent1.display = browser.getDisplay();
521 newEvent1.widget = browser;
522 newEvent1.location = url1;
523 newEvent1.doit = true;
524 for (LocationListener locationListener : locationListeners) {
525 locationListener.changing(newEvent1);
526 }
527 boolean doit1 = newEvent1.doit && !browser.isDisposed();
528 Variant cancel3 = event.arguments[6];
529 if (cancel3 != null) {
530 long pCancel3 = cancel3.getByRef();
531 OS.MoveMemory(pCancel3, new short[] {doit1 ? OS.VARIANT_FALSE : OS.VARIANT_TRUE}, 2);
532 }
533 if (doit1) {
534 varResult1 = event.arguments[0];
535 IDispatch dispatch1 = varResult1.getDispatch();
536 Variant variant1 = new Variant(auto); /* does not need to be disposed */
537 IDispatch top1 = variant1.getDispatch();
538 if (top1.getAddress() == dispatch1.getAddress()) {
539 setAboutBlank(url1.startsWith(ABOUT_BLANK));
540 }
541 }
542 break;
543 }
544 case CommandStateChange: {
545 boolean enabled = false;
546 Variant varResult2 = event.arguments[0];
547 int command = varResult2.getInt();
548 varResult2 = event.arguments[1];
549 enabled = varResult2.getBoolean();
550 switch (command) {
551 case CSC_NAVIGATEBACK : back = enabled; break;
552 case CSC_NAVIGATEFORWARD : forward = enabled; break;
553 }
554 break;
555 }
556 case DocumentComplete: {
557 if (performingInitialNavigate) {
558 /* this event marks the completion of the initial navigate to about:blank */
559 performingInitialNavigate = false;
560
561 /* if browser content has been provided by the client then set it now */
562 if (pendingText != null) {
563 setText((String)pendingText[0], ((Boolean)pendingText[1]).booleanValue());
564 } else if (pendingUrl != null) {
565 setUrl((String)pendingUrl[0], (String)pendingUrl[1], (String[])pendingUrl[2]);
566 }
567 pendingText = pendingUrl = null;
568 break;
569 }
570
571 Variant varResult3 = event.arguments[0];
572 IDispatch dispatch2 = varResult3.getDispatch();
573
574 varResult3 = event.arguments[1];
575 String url2 = varResult3.getString();
576 /*
577 * Feature in IE. For navigations on the local machine, DocumentComplete's url
578 * field contains a string representation of the file path in a non-URL format.
579 * In order to be consistent with the other Browser implementations, this
580 * case is detected and the string is changed to be a proper url string.
581 */
582 if (url2.indexOf(":/") == -1 && url2.indexOf(":\\") != -1) { //$NON-NLS-1$ //$NON-NLS-2$
583 TCHAR filePath2 = new TCHAR(0, url2, true);
584 TCHAR urlResult2 = new TCHAR(0, OS.INTERNET_MAX_URL_LENGTH);
585 int[] size2 = new int[] {urlResult2.length()};
586 if (OS.UrlCreateFromPath(filePath2, urlResult2, size2, 0) == COM.S_OK) {
587 url2 = urlResult2.toString(0, size2[0]);
588 } else {
589 url2 = PROTOCOL_FILE + url2.replace('\\', '/');
590 }
591 }
592 if (html != null && url2.equals(ABOUT_BLANK)) {
593 if (delaySetText) {
594 delaySetText = false;
595 browser.getDisplay().asyncExec(() -> {
596 if (browser.isDisposed() || html == null) return;
597 setHTML(html);
598 html = null;
599 });
600 } else {
601 setHTML(html);
602 html = null;
603 }
604 } else {
605 Variant variant2 = new Variant(auto); /* does not need to be disposed */
606 IDispatch top2 = variant2.getDispatch();
607 LocationEvent locationEvent = new LocationEvent(browser);
608 locationEvent.display = browser.getDisplay();
609 locationEvent.widget = browser;
610 locationEvent.location = url2;
611 locationEvent.top = top2.getAddress() == dispatch2.getAddress();
612 for (LocationListener locationListener : locationListeners) {
613 locationListener.changed(locationEvent);
614 }
615 if (browser.isDisposed()) return;
616
617 /*
618 * With the IBM 64-bit JVM an unexpected document complete event occurs before
619 * the native browser's DOM has been built. Filter this premature event based
620 * on the browser's ready state.
621 */
622 int[] rgdispid1 = auto.getIDsOfNames(new String[] { "ReadyState" }); //$NON-NLS-1$
623 Variant pVarResult1 = auto.getProperty(rgdispid1[0]);
624 if (pVarResult1 != null) {
625 int readyState = pVarResult1.getInt();
626 pVarResult1.dispose ();
627 if (readyState != READYSTATE_COMPLETE) {
628 break;
629 }
630 }
631
632 /*
633 * Note. The completion of the page loading is detected as
634 * described in the MSDN article "Determine when a page is
635 * done loading in WebBrowser Control".
636 */
637 if (globalDispatch != 0 && dispatch2.getAddress() == globalDispatch) {
638 /* final document complete */
639 globalDispatch = 0;
640
641 /* re-install registered functions iff needed */
642 IE ie = (IE)browser.webBrowser;
643 if (ie.installFunctionsOnDocumentComplete) {
644 ie.installFunctionsOnDocumentComplete = false;
645 Iterator<BrowserFunction> elements1 = functions.values().iterator ();
646 while (elements1.hasNext ()) {
647 BrowserFunction function1 = elements1.next ();
648 execute (function1.functionString);
649 }
650 }
651
652 ProgressEvent progressEvent1 = new ProgressEvent(browser);
653 progressEvent1.display = browser.getDisplay();
654 progressEvent1.widget = browser;
655 for (ProgressListener progressListener : progressListeners) {
656 progressListener.completed(progressEvent1);
657 }
658 }
659 }
660 break;
661 }
662 case DownloadComplete: {
663 /*
664 * IE feature. Some events that swt relies on are not sent when
665 * a page is refreshed (as opposed to being navigated to). The
666 * workaround is to use DownloadComplete as an opportunity to
667 * do this work.
668 */
669
670 Iterator<BrowserFunction> elements2 = functions.values().iterator ();
671 while (elements2.hasNext ()) {
672 BrowserFunction function2 = elements2.next ();
673 execute (function2.functionString);
674 }
675
676 if (!isRefresh) break;
677 isRefresh = false;
678
679 /*
680 * DocumentComplete is not received for refreshes, but clients may rely
681 * on this event for tasks like hooking javascript listeners, so send the
682 * event here.
683 */
684 ProgressEvent progressEvent2 = new ProgressEvent(browser);
685 progressEvent2.display = browser.getDisplay();
686 progressEvent2.widget = browser;
687 for (ProgressListener progressListener : progressListeners) {
688 progressListener.completed(progressEvent2);
689 }
690
691 break;
692 }
693 case NavigateComplete2: {
694 jsEnabled = jsEnabledOnNextPage;
695
696 Variant varResult4 = event.arguments[1];
697 String url3 = varResult4.getString();
698 if (!performingInitialNavigate) {
699 varResult4 = event.arguments[0];
700 IDispatch dispatch3 = varResult4.getDispatch();
701 Variant variant3 = new Variant(auto); /* does not need to be disposed */
702 IDispatch top3 = variant3.getDispatch();
703 if (top3.getAddress() == dispatch3.getAddress()) {
704 setAboutBlank(url3.startsWith(ABOUT_BLANK));
705 lastNavigateURL = url3;
706 }
707 }
708
709 /*
710 * Bug in Acrobat Reader. Opening > MAX_PDF PDF files causes Acrobat to not
711 * clean up its shells properly when the container Browser is disposed.
712 * This results in Eclipse crashing at shutdown time because the leftover
713 * shells have invalid references to unloaded Acrobat libraries. The
714 * workaround is to not unload the Acrobat libraries if > MAX_PDF PDF
715 * files have been opened.
716 */
717 boolean isPDF = false;
718 String path = null;
719 try {
720 path = new URL(url3).getPath();
721 } catch (MalformedURLException e) {
722 }
723 if (path != null) {
724 int extensionIndex = path.lastIndexOf('.');
725 if (extensionIndex != -1) {
726 String extension = path.substring(extensionIndex);
727 if (extension.equalsIgnoreCase(EXTENSION_PDF)) {
728 isPDF = true;
729 PDFCount++;
730 if (PDFCount > MAX_PDF) {
731 COM.FreeUnusedLibraries = false;
732 }
733 }
734 }
735 }
736
737 if (uncRedirect != null) {
738 if (uncRedirect.equals(url3)) {
739 /* full UNC path has been successfully navigated */
740 uncRedirect = null;
741 break;
742 }
743 if (uncRedirect.startsWith(url3)) {
744 /*
745 * UNC first segment has been successfully navigated,
746 * now redirect to the full UNC path.
747 */
748 navigate(uncRedirect, null, null, true);
749 break;
750 }
751 uncRedirect = null;
752 }
753
754 varResult4 = event.arguments[0];
755 IDispatch dispatch4 = varResult4.getDispatch();
756 if (globalDispatch == 0) globalDispatch = dispatch4.getAddress();
757
758 OleAutomation webBrowser = varResult4.getAutomation();
759 Variant variant4 = new Variant(auto); /* does not need to be disposed */
760 IDispatch top4 = variant4.getDispatch();
761 boolean isTop = top4.getAddress() == dispatch4.getAddress();
762 if (isTop) {
763 /* unhook DOM listeners and unref the last document(s) */
764 unhookDOMListeners(documents);
765 for (OleAutomation document : documents) {
766 document.dispose();
767 }
768 documents = new OleAutomation[0];
769
770 /* re-install registered functions */
771 Iterator<BrowserFunction> elements3 = functions.values().iterator ();
772 while (elements3.hasNext ()) {
773 BrowserFunction function3 = elements3.next ();
774 execute (function3.functionString);
775 }
776 }
777 if (!isPDF) {
778 hookDOMListeners(webBrowser, isTop);
779 }
780 webBrowser.dispose();
781 break;
782 }
783 case NavigateError: {
784 if (uncRedirect != null) {
785 /*
786 * This is the second error attempting to reach this UNC path, so
787 * it does not exist. Don't override the default error handling.
788 */
789 uncRedirect = null;
790 break;
791 }
792 Variant varResult5 = event.arguments[1];
793 final String url4 = varResult5.getString();
794 if (url4.startsWith("\\\\")) { //$NON-NLS-1$
795 varResult5 = event.arguments[3];
796 int statusCode = varResult5.getInt();
797 if (statusCode == INET_E_RESOURCE_NOT_FOUND) {
798 int index = url4.indexOf('\\', 2);
799 if (index != -1) {
800 final String host = url4.substring(0, index);
801 Variant cancel4 = event.arguments[4];
802 if (cancel4 != null) {
803 long pCancel4 = cancel4.getByRef();
804 OS.MoveMemory(pCancel4, new short[] {OS.VARIANT_TRUE}, 2);
805 }
806 browser.getDisplay().asyncExec(() -> {
807 if (browser.isDisposed()) return;
808 /*
809 * Feature of IE. When a UNC path ends with a '\' character IE
810 * drops this character when providing the path as an argument
811 * to some IE listeners. Remove this character here too in
812 * order to match these other listener argument values.
813 */
814 if (url4.endsWith("\\")) { //$NON-NLS-1$
815 uncRedirect = url4.substring(0, url4.length() - 1);
816 } else {
817 uncRedirect = url4;
818 }
819 navigate(host, null, null, true);
820 });
821 }
822 }
823 }
824 break;
825 }
826 case NewWindow2: {
827 Variant cancel5 = event.arguments[1];
828 long pCancel5 = cancel5.getByRef();
829 WindowEvent newEvent2 = new WindowEvent(browser);
830 newEvent2.display = browser.getDisplay();
831 newEvent2.widget = browser;
832 newEvent2.required = false;
833 for (OpenWindowListener openWindowListener : openWindowListeners) {
834 openWindowListener.open(newEvent2);
835 }
836 IE browser = null;
837 if (newEvent2.browser != null && newEvent2.browser.webBrowser instanceof IE) {
838 browser = (IE)newEvent2.browser.webBrowser;
839 }
840 boolean doit2 = browser != null && !browser.browser.isDisposed();
841 if (doit2) {
842 /*
843 * When a Browser is opened in a new window, BrowserFunctions that are
844 * installed in it in the NavigateComplete2 callback are not retained
845 * through the loading of the page. The workaround is to re-install
846 * the functions when DocumentComplete is received.
847 */
848 browser.installFunctionsOnDocumentComplete = true;
849
850 Variant variant5 = new Variant(browser.auto); /* does not need to be disposed */
851 IDispatch iDispatch = variant5.getDispatch();
852 Variant ppDisp = event.arguments[0];
853 long byref = ppDisp.getByRef();
854 if (byref != 0) OS.MoveMemory(byref, new long[] {iDispatch.getAddress()}, C.PTR_SIZEOF);
855 }
856 if (newEvent2.required) {
857 OS.MoveMemory(pCancel5, new short[]{doit2 ? OS.VARIANT_FALSE : OS.VARIANT_TRUE}, 2);
858 }
859 break;
860 }
861 case OnMenuBar: {
862 Variant arg01 = event.arguments[0];
863 menuBar = arg01.getBoolean();
864 break;
865 }
866 case OnStatusBar: {
867 Variant arg02 = event.arguments[0];
868 statusBar = arg02.getBoolean();
869 break;
870 }
871 case OnToolBar: {
872 Variant arg03 = event.arguments[0];
873 toolBar = arg03.getBoolean();
874 /*
875 * Feature in Internet Explorer. OnToolBar FALSE is emitted
876 * when both tool bar, address bar and menu bar must not be visible.
877 * OnToolBar TRUE is emitted when either of tool bar, address bar
878 * or menu bar is visible.
879 */
880 if (!toolBar) {
881 addressBar = false;
882 menuBar = false;
883 }
884 break;
885 }
886 case OnVisible: {
887 Variant arg11 = event.arguments[0];
888 boolean visible = arg11.getBoolean();
889 WindowEvent newEvent3 = new WindowEvent(browser);
890 newEvent3.display = browser.getDisplay();
891 newEvent3.widget = browser;
892 if (visible) {
893 if (addressBar) {
894 /*
895 * Bug in Internet Explorer. There is no distinct notification for
896 * the address bar. If neither address, menu or tool bars are visible,
897 * OnToolBar FALSE is emitted. For some reason, querying the value of
898 * AddressBar in this case returns true even though it should not be
899 * set visible. The workaround is to only query the value of AddressBar
900 * when OnToolBar FALSE has not been emitted.
901 */
902 int[] rgdispid2 = auto.getIDsOfNames(new String[] { "AddressBar" }); //$NON-NLS-1$
903 Variant pVarResult2 = auto.getProperty(rgdispid2[0]);
904 if (pVarResult2 != null) {
905 if (pVarResult2.getType () == OLE.VT_BOOL) {
906 addressBar = pVarResult2.getBoolean ();
907 }
908 pVarResult2.dispose ();
909 }
910 }
911 newEvent3.addressBar = addressBar;
912 newEvent3.menuBar = menuBar;
913 newEvent3.statusBar = statusBar;
914 newEvent3.toolBar = toolBar;
915 newEvent3.location = location;
916 newEvent3.size = size;
917 for (VisibilityWindowListener visibilityWindowListener : visibilityWindowListeners) {
918 visibilityWindowListener.show(newEvent3);
919 }
920 location = null;
921 size = null;
922 } else {
923 for (VisibilityWindowListener visibilityWindowListener : visibilityWindowListeners) {
924 visibilityWindowListener.hide(newEvent3);
925 }
926 }
927 break;
928 }
929 case ProgressChange: {
930 /* don't send client events if the initial navigate to about:blank has not completed */
931 if (performingInitialNavigate) break;
932
933 Variant arg12 = event.arguments[0];
934 int nProgress = arg12.getType() != OLE.VT_I4 ? 0 : arg12.getInt(); // may be -1
935 Variant arg2 = event.arguments[1];
936 int nProgressMax = arg2.getType() != OLE.VT_I4 ? 0 : arg2.getInt();
937 ProgressEvent newEvent4 = new ProgressEvent(browser);
938 newEvent4.display = browser.getDisplay();
939 newEvent4.widget = browser;
940 newEvent4.current = nProgress;
941 newEvent4.total = nProgressMax;
942 if (nProgress != -1) {
943 for (ProgressListener progressListener : progressListeners) {
944 progressListener.changed(newEvent4);
945 }
946 }
947 break;
948 }
949 case StatusTextChange: {
950 /* don't send client events if the initial navigate to about:blank has not completed */
951 if (performingInitialNavigate) break;
952
953 Variant arg13 = event.arguments[0];
954 if (arg13.getType() == OLE.VT_BSTR) {
955 String text = arg13.getString();
956 StatusTextEvent newEvent5 = new StatusTextEvent(browser);
957 newEvent5.display = browser.getDisplay();
958 newEvent5.widget = browser;
959 newEvent5.text = text;
960 for (StatusTextListener statusTextListener : statusTextListeners) {
961 statusTextListener.changed(newEvent5);
962 }
963 }
964 break;
965 }
966 case TitleChange: {
967 /* don't send client events if the initial navigate to about:blank has not completed */
968 if (performingInitialNavigate) break;
969
970 Variant arg14 = event.arguments[0];
971 if (arg14.getType() == OLE.VT_BSTR) {
972 String title = arg14.getString();
973 TitleEvent newEvent6 = new TitleEvent(browser);
974 newEvent6.display = browser.getDisplay();
975 newEvent6.widget = browser;
976 newEvent6.title = title;
977 for (TitleListener titleListener : titleListeners) {
978 titleListener.changed(newEvent6);
979 }
980 }
981 break;
982 }
983 case WindowClosing: {
984 /*
985 * Disposing the Browser directly from this callback will crash if the
986 * Browser has a text field with an active caret. As a workaround fire
987 * the Close event and dispose the Browser in an async block.
988 */
989 browser.getDisplay().asyncExec(() -> {
990 if (browser.isDisposed()) return;
991 WindowEvent newEvent = new WindowEvent(browser);
992 newEvent.display = browser.getDisplay();
993 newEvent.widget = browser;
994 for (CloseWindowListener closeWindowListener : closeWindowListeners) {
995 closeWindowListener.close(newEvent);
996 }
997 browser.dispose();
998 });
999 Variant cancel6 = event.arguments[1];
1000 long pCancel6 = cancel6.getByRef();
1001 Variant arg15 = event.arguments[0];
1002 boolean isChildWindow = arg15.getBoolean();
1003 OS.MoveMemory(pCancel6, new short[]{isChildWindow ? OS.VARIANT_FALSE : OS.VARIANT_TRUE}, 2);
1004 break;
1005 }
1006 case WindowSetHeight: {
1007 if (size == null) size = new Point(0, 0);
1008 Variant arg16 = event.arguments[0];
1009 size.y = arg16.getInt();
1010 break;
1011 }
1012 case WindowSetLeft: {
1013 if (location == null) location = new Point(0, 0);
1014 Variant arg17 = event.arguments[0];
1015 location.x = arg17.getInt();
1016 break;
1017 }
1018 case WindowSetTop: {
1019 if (location == null) location = new Point(0, 0);
1020 Variant arg18 = event.arguments[0];
1021 location.y = arg18.getInt();
1022 break;
1023 }
1024 case WindowSetWidth: {
1025 if (size == null) size = new Point(0, 0);
1026 Variant arg19 = event.arguments[0];
1027 size.x = arg19.getInt();
1028 break;
1029 }
1030 }
1031 }
1032 };
1033 site.addEventListener(BeforeNavigate2, oleListener);
1034 site.addEventListener(CommandStateChange, oleListener);
1035 site.addEventListener(DocumentComplete, oleListener);
1036 site.addEventListener(DownloadComplete, oleListener);
1037 site.addEventListener(NavigateComplete2, oleListener);
1038 site.addEventListener(NavigateError, oleListener);
1039 site.addEventListener(NewWindow2, oleListener);
1040 site.addEventListener(OnMenuBar, oleListener);
1041 site.addEventListener(OnStatusBar, oleListener);
1042 site.addEventListener(OnToolBar, oleListener);
1043 site.addEventListener(OnVisible, oleListener);
1044 site.addEventListener(ProgressChange, oleListener);
1045 site.addEventListener(StatusTextChange, oleListener);
1046 site.addEventListener(TitleChange, oleListener);
1047 site.addEventListener(WindowClosing, oleListener);
1048 site.addEventListener(WindowSetHeight, oleListener);
1049 site.addEventListener(WindowSetLeft, oleListener);
1050 site.addEventListener(WindowSetTop, oleListener);
1051 site.addEventListener(WindowSetWidth, oleListener);
1052
1053 Variant variant = new Variant(true);
1054 auto.setProperty(RegisterAsBrowser, variant);
1055 variant.dispose();
1056
1057 variant = new Variant(false);
1058 int[] rgdispid = auto.getIDsOfNames(new String[] {"RegisterAsDropTarget"}); //$NON-NLS-1$
1059 if (rgdispid != null) auto.setProperty(rgdispid[0], variant);
1060 variant.dispose();
1061 }
1062
1063 @Override
back()1064 public boolean back() {
1065 if (!back) return false;
1066 int[] rgdispid = auto.getIDsOfNames(new String[] { "GoBack" }); //$NON-NLS-1$
1067 Variant pVarResult = auto.invoke(rgdispid[0]);
1068 return pVarResult != null && pVarResult.getType() == OLE.VT_EMPTY;
1069 }
1070
1071 @Override
close()1072 public boolean close() {
1073 boolean result = true;
1074 int[] rgdispid = auto.getIDsOfNames(new String[] {PROPERTY_DOCUMENT});
1075 int dispIdMember = rgdispid[0];
1076 Variant pVarResult = auto.getProperty(dispIdMember);
1077 if (pVarResult == null || pVarResult.getType() == COM.VT_EMPTY) {
1078 if (pVarResult != null) pVarResult.dispose();
1079 } else {
1080 OleAutomation document = pVarResult.getAutomation();
1081 pVarResult.dispose();
1082 rgdispid = document.getIDsOfNames(new String[]{"parentWindow"}); //$NON-NLS-1$
1083 /* rgdispid != null implies HTML content */
1084 if (rgdispid != null) {
1085 dispIdMember = rgdispid[0];
1086 pVarResult = document.getProperty(dispIdMember);
1087 if (pVarResult == null || pVarResult.getType() == COM.VT_EMPTY) {
1088 if (pVarResult != null) pVarResult.dispose();
1089 } else {
1090 OleAutomation window = pVarResult.getAutomation();
1091 pVarResult.dispose();
1092 rgdispid = window.getIDsOfNames(new String[]{"location"}); //$NON-NLS-1$
1093 dispIdMember = rgdispid[0];
1094 pVarResult = window.getProperty(dispIdMember);
1095 if (pVarResult == null || pVarResult.getType() == COM.VT_EMPTY) {
1096 if (pVarResult != null) pVarResult.dispose();
1097 } else {
1098 OleAutomation location = pVarResult.getAutomation();
1099 pVarResult.dispose();
1100 LocationListener[] oldListeners = locationListeners;
1101 locationListeners = new LocationListener[0];
1102 rgdispid = location.getIDsOfNames(new String[]{"replace"}); //$NON-NLS-1$
1103 dispIdMember = rgdispid[0];
1104 Variant[] args = new Variant[] {new Variant("about:blank")}; //$NON-NLS-1$
1105 pVarResult = location.invoke(dispIdMember, args);
1106 if (pVarResult == null) {
1107 /* cancelled by user */
1108 result = false;
1109 } else {
1110 pVarResult.dispose();
1111 }
1112 args[0].dispose();
1113 locationListeners = oldListeners;
1114 location.dispose();
1115 }
1116 window.dispose();
1117 }
1118 }
1119 document.dispose();
1120 }
1121 return result;
1122 }
1123
createSafeArray(String string)1124 static Variant createSafeArray(String string) {
1125 /* Create a pointer and copy the data into it */
1126 byte[] bytes = string.getBytes();
1127 int length = bytes.length;
1128 long pvData = OS.GlobalAlloc(OS.GMEM_FIXED | OS.GMEM_ZEROINIT, length);
1129 C.memmove(pvData, bytes, length);
1130 int cElements1 = length;
1131
1132 /* Create a SAFEARRAY in memory */
1133 long pSafeArray = OS.GlobalAlloc(OS.GMEM_FIXED | OS.GMEM_ZEROINIT, SAFEARRAY.sizeof);
1134 SAFEARRAY safeArray = new SAFEARRAY();
1135 safeArray.cDims = 1;
1136 safeArray.fFeatures = OS.FADF_FIXEDSIZE;
1137 safeArray.cbElements = 1;
1138 safeArray.pvData = pvData;
1139 SAFEARRAYBOUND safeArrayBound = new SAFEARRAYBOUND();
1140 safeArray.rgsabound = safeArrayBound;
1141 safeArrayBound.cElements = cElements1;
1142 OS.MoveMemory (pSafeArray, safeArray, SAFEARRAY.sizeof);
1143
1144 /* Return a Variant that holds the SAFEARRAY */
1145 long pVariant = OS.GlobalAlloc(OS.GMEM_FIXED | OS.GMEM_ZEROINIT, Variant.sizeof);
1146 short vt = (short)(OLE.VT_ARRAY | OLE.VT_UI1);
1147 OS.MoveMemory(pVariant, new short[] {vt}, 2);
1148 OS.MoveMemory(pVariant + 8, new long[] {pSafeArray}, C.PTR_SIZEOF);
1149 return new Variant(pVariant, (short)(OLE.VT_BYREF | OLE.VT_VARIANT));
1150 }
1151
1152 @Override
execute(String script)1153 public boolean execute(String script) {
1154 /*
1155 * Issue with IE: If the browser has not shown any content yet then
1156 * first navigate to about:blank to work around bug 465822, then execute
1157 * the requested script.
1158 */
1159 if (!performingInitialNavigate && _getUrl().length() == 0) {
1160 performingInitialNavigate = true;
1161 navigate (ABOUT_BLANK, null, null, true);
1162 }
1163
1164 /* get IHTMLDocument2 */
1165 int[] rgdispid = auto.getIDsOfNames(new String[] {PROPERTY_DOCUMENT});
1166 int dispIdMember = rgdispid[0];
1167 Variant pVarResult = auto.getProperty(dispIdMember);
1168 if (pVarResult == null || pVarResult.getType() == COM.VT_EMPTY) {
1169 if (pVarResult != null) pVarResult.dispose ();
1170 return false;
1171 }
1172 OleAutomation document = pVarResult.getAutomation();
1173 pVarResult.dispose();
1174
1175 /* get IHTMLWindow2 */
1176 rgdispid = document.getIDsOfNames(new String[]{"parentWindow"}); //$NON-NLS-1$
1177 if (rgdispid == null) {
1178 /* implies that browser's content is not a IHTMLDocument2 (eg.- acrobat reader) */
1179 document.dispose();
1180 return false;
1181 }
1182 dispIdMember = rgdispid[0];
1183 pVarResult = document.getProperty(dispIdMember);
1184 if (pVarResult == null || pVarResult.getType() == COM.VT_EMPTY) {
1185 if (pVarResult != null) pVarResult.dispose ();
1186 document.dispose();
1187 return false;
1188 }
1189 OleAutomation ihtmlWindow2 = pVarResult.getAutomation();
1190 pVarResult.dispose();
1191 document.dispose();
1192
1193 rgdispid = ihtmlWindow2.getIDsOfNames(new String[] { "execScript", "code" }); //$NON-NLS-1$ //$NON-NLS-2$
1194 if (rgdispid == null) {
1195 ihtmlWindow2.dispose();
1196 return false;
1197 }
1198 Variant[] rgvarg = new Variant[1];
1199 rgvarg[0] = new Variant(script);
1200 int[] rgdispidNamedArgs = new int[1];
1201 rgdispidNamedArgs[0] = rgdispid[1];
1202 pVarResult = ihtmlWindow2.invoke(rgdispid[0], rgvarg, rgdispidNamedArgs);
1203 rgvarg[0].dispose();
1204 ihtmlWindow2.dispose();
1205 if (pVarResult == null) return false;
1206 pVarResult.dispose();
1207 return true;
1208 }
1209
1210 @Override
forward()1211 public boolean forward() {
1212 if (!forward) return false;
1213 int[] rgdispid = auto.getIDsOfNames(new String[] { "GoForward" }); //$NON-NLS-1$
1214 Variant pVarResult = auto.invoke(rgdispid[0]);
1215 return pVarResult != null && pVarResult.getType() == OLE.VT_EMPTY;
1216 }
1217
1218 @Override
getBrowserType()1219 public String getBrowserType () {
1220 return "ie"; //$NON-NLS-1$
1221 }
1222
1223 @Override
getDeleteFunctionString(String functionName)1224 String getDeleteFunctionString (String functionName) {
1225 return "window." + functionName + "=undefined"; //$NON-NLS-1$ //$NON-NLS-2$
1226 }
1227
1228 @Override
getText()1229 public String getText() {
1230 /* get the document object */
1231 int[] rgdispid = auto.getIDsOfNames(new String[] {PROPERTY_DOCUMENT});
1232 Variant pVarResult = auto.getProperty(rgdispid[0]);
1233 if (pVarResult == null || pVarResult.getType() == COM.VT_EMPTY) {
1234 if (pVarResult != null) pVarResult.dispose ();
1235 return ""; //$NON-NLS-1$
1236 }
1237 OleAutomation document = pVarResult.getAutomation();
1238 pVarResult.dispose();
1239
1240 /* get the html object */
1241 rgdispid = document.getIDsOfNames(new String[] {"documentElement"}); //$NON-NLS-1$
1242 if (rgdispid == null) {
1243 /* implies that the browser is displaying non-HTML content */
1244 document.dispose();
1245 return ""; //$NON-NLS-1$
1246 }
1247 pVarResult = document.getProperty(rgdispid[0]);
1248 document.dispose();
1249 if (pVarResult == null || pVarResult.getType() == COM.VT_EMPTY || pVarResult.getType() == COM.VT_NULL) {
1250 if (pVarResult != null) pVarResult.dispose ();
1251 return ""; //$NON-NLS-1$
1252 }
1253 OleAutomation element = pVarResult.getAutomation();
1254 pVarResult.dispose();
1255
1256 /* get its outerHTML property */
1257 rgdispid = element.getIDsOfNames(new String[] {"outerHTML"}); //$NON-NLS-1$
1258 pVarResult = element.getProperty(rgdispid[0]);
1259 element.dispose();
1260 if (pVarResult == null || pVarResult.getType() == COM.VT_EMPTY) {
1261 if (pVarResult != null) pVarResult.dispose ();
1262 return ""; //$NON-NLS-1$
1263 }
1264 String result = pVarResult.getString();
1265 pVarResult.dispose();
1266
1267 return result;
1268 }
1269
1270 @Override
getUrl()1271 public String getUrl() {
1272 /*
1273 * If the url is "" then return ABOUT_BLANK in order to be consistent
1274 * with the other Browser implementations which auto-navigate to ABOUT_BLANK
1275 * when opened.
1276 */
1277 String result = _getUrl();
1278 return result.length() != 0 ? result : ABOUT_BLANK;
1279 }
1280
_getUrl()1281 String _getUrl() {
1282 int[] rgdispid = auto.getIDsOfNames(new String[] { "LocationURL" }); //$NON-NLS-1$
1283 Variant pVarResult = auto.getProperty(rgdispid[0]);
1284 if (pVarResult == null || pVarResult.getType() != OLE.VT_BSTR) return ""; //$NON-NLS-1$
1285 String result = pVarResult.getString();
1286 pVarResult.dispose();
1287 return result;
1288 }
1289
1290 @Override
isBackEnabled()1291 public boolean isBackEnabled() {
1292 return back;
1293 }
1294
1295 @Override
isForwardEnabled()1296 public boolean isForwardEnabled() {
1297 return forward;
1298 }
1299
1300 @Override
isFocusControl()1301 public boolean isFocusControl () {
1302 return site.isFocusControl() || frame.isFocusControl();
1303 }
1304
navigate(String url, String postData, String headers[], boolean silent)1305 boolean navigate(String url, String postData, String headers[], boolean silent) {
1306 int count = 1;
1307 if (postData != null) count++;
1308 if (headers != null) count++;
1309 Variant[] rgvarg = new Variant[count];
1310 int[] rgdispidNamedArgs = new int[count];
1311 int[] rgdispid = auto.getIDsOfNames(new String[] { "Navigate", "URL", "PostData", "Headers" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
1312 int index = 0;
1313 rgvarg[index] = new Variant(url);
1314 rgdispidNamedArgs[index++] = rgdispid[1];
1315 if (postData != null) {
1316 rgvarg[index] = createSafeArray(postData);
1317 rgdispidNamedArgs[index++] = rgdispid[2];
1318 }
1319 if (headers != null) {
1320 StringBuilder buffer = new StringBuilder();
1321 for (String current : headers) {
1322 if (current != null) {
1323 int sep = current.indexOf(':');
1324 if (sep != -1) {
1325 String key = current.substring(0, sep).trim();
1326 String value = current.substring(sep + 1).trim();
1327 if (key.length() > 0 && value.length() > 0) {
1328 buffer.append(key);
1329 buffer.append(':');
1330 buffer.append(value);
1331 buffer.append("\r\n");
1332 }
1333 }
1334 }
1335 }
1336 rgvarg[index] = new Variant(buffer.toString());
1337 rgdispidNamedArgs[index++] = rgdispid[3];
1338 }
1339 boolean oldValue = false;
1340 if (silent && IEVersion >= 7) {
1341 int hResult = OS.CoInternetIsFeatureEnabled(OS.FEATURE_DISABLE_NAVIGATION_SOUNDS, OS.GET_FEATURE_FROM_PROCESS);
1342 oldValue = hResult == COM.S_OK;
1343 OS.CoInternetSetFeatureEnabled(OS.FEATURE_DISABLE_NAVIGATION_SOUNDS, OS.SET_FEATURE_ON_PROCESS, true);
1344 }
1345 Variant pVarResult = auto.invoke(rgdispid[0], rgvarg, rgdispidNamedArgs);
1346 if (silent && IEVersion >= 7) {
1347 OS.CoInternetSetFeatureEnabled(OS.FEATURE_DISABLE_NAVIGATION_SOUNDS, OS.SET_FEATURE_ON_PROCESS, oldValue);
1348 }
1349 for (int i = 0; i < count; i++) {
1350 rgvarg[i].dispose();
1351 }
1352 if (pVarResult == null) return false;
1353 boolean result = pVarResult.getType() == OLE.VT_EMPTY;
1354 pVarResult.dispose();
1355 return result;
1356 }
1357
1358 @Override
refresh()1359 public void refresh() {
1360 uncRedirect = null;
1361
1362 /*
1363 * Bug in Acrobat Reader. Opening > MAX_PDF PDF files causes Acrobat to not
1364 * clean up its shells properly when the container Browser is disposed.
1365 * This results in Eclipse crashing at shutdown time because the leftover
1366 * shells have invalid references to unloaded Acrobat libraries. The
1367 * workaround is to not unload the Acrobat libraries if > MAX_PDF PDF
1368 * files have been opened.
1369 */
1370 String url = _getUrl();
1371 int extensionIndex = url.lastIndexOf('.');
1372 if (extensionIndex != -1) {
1373 String extension = url.substring(extensionIndex);
1374 if (extension.equalsIgnoreCase (EXTENSION_PDF)) {
1375 PDFCount++;
1376 if (PDFCount > MAX_PDF) {
1377 COM.FreeUnusedLibraries = false;
1378 }
1379 }
1380 }
1381
1382 isRefresh = true;
1383 int[] rgdispid = auto.getIDsOfNames(new String[] { "Refresh" }); //$NON-NLS-1$
1384 auto.invoke(rgdispid[0]);
1385 }
1386
setHTML(String string)1387 void setHTML (String string) {
1388 int charCount = string.length();
1389 char[] chars = new char[charCount];
1390 string.getChars(0, charCount, chars, 0);
1391 int byteCount = OS.WideCharToMultiByte(OS.CP_UTF8, 0, chars, charCount, null, 0, null, null);
1392 /*
1393 * Internet Explorer appears to treat the data loaded with
1394 * nsIPersistStreamInit.Load as if it were encoded using the default
1395 * local charset. There does not seem to be an API to set the
1396 * desired charset explicitly in this case. The fix is to
1397 * prepend the UTF-8 Byte Order Mark signature to the data.
1398 */
1399 byte[] UTF8BOM = {(byte)0xEF, (byte)0xBB, (byte)0xBF};
1400 long hGlobal = OS.GlobalAlloc(OS.GMEM_FIXED | OS.GMEM_ZEROINIT, UTF8BOM.length + byteCount);
1401 if (hGlobal != 0) {
1402 OS.MoveMemory(hGlobal, UTF8BOM, UTF8BOM.length);
1403 OS.WideCharToMultiByte(OS.CP_UTF8, 0, chars, charCount, hGlobal + UTF8BOM.length, byteCount, null, null);
1404 long [] ppstm = new long [1];
1405 /*
1406 * CreateStreamOnHGlobal is called with the flag fDeleteOnRelease.
1407 * If the call succeeds the buffer hGlobal is freed automatically
1408 * when the IStream object is released. If the call fails, free the
1409 * buffer hGlobal.
1410 */
1411 if (OS.CreateStreamOnHGlobal(hGlobal, true, ppstm) == OS.S_OK) {
1412 int[] rgdispid = auto.getIDsOfNames(new String[] {PROPERTY_DOCUMENT});
1413 Variant pVarResult = auto.getProperty(rgdispid[0]);
1414 IDispatch dispatchDocument = pVarResult.getDispatch();
1415 long [] ppvObject = new long [1];
1416 int result = dispatchDocument.QueryInterface(COM.IIDIPersistStreamInit, ppvObject);
1417 if (result == OS.S_OK) {
1418 IPersistStreamInit persistStreamInit = new IPersistStreamInit(ppvObject[0]);
1419 if (persistStreamInit.InitNew() == OS.S_OK) {
1420 persistStreamInit.Load(ppstm[0]);
1421 }
1422 persistStreamInit.Release();
1423 }
1424 pVarResult.dispose();
1425 IUnknown stream = new IUnknown(ppstm[0]);
1426 stream.Release();
1427 } else {
1428 OS.GlobalFree(hGlobal);
1429 }
1430 }
1431 }
1432
setAboutBlank(boolean value)1433 private void setAboutBlank(boolean value) {
1434 isAboutBlank = value;
1435 updateForceTrusted();
1436 }
1437
setUntrustedText(boolean value)1438 private void setUntrustedText(boolean value) {
1439 untrustedText = value;
1440 updateForceTrusted();
1441 }
1442
updateForceTrusted()1443 private void updateForceTrusted() {
1444 site.isForceTrusted = isAboutBlank && !untrustedText;
1445 }
1446
1447 @Override
setText(final String html, boolean trusted)1448 public boolean setText(final String html, boolean trusted) {
1449 /*
1450 * If the browser is navigating to about:blank in response to its first
1451 * setUrl() invocation then delay setting this text content until the
1452 * navigate has completed. about:blank will be re-navigated to in order
1453 * to ensure that all expected client events are sent.
1454 */
1455 if (performingInitialNavigate) {
1456 pendingText = new Object[] {html, trusted};
1457 pendingUrl = null;
1458 return true;
1459 }
1460
1461 /*
1462 * If the html field is non-null then the about:blank page is already being
1463 * loaded from a previous setText() invocation, so no Stop or Navigate is
1464 * required. Just set the html that is to be shown.
1465 */
1466 boolean blankLoading = this.html != null;
1467 this.html = html;
1468 setUntrustedText(!trusted);
1469 if (blankLoading) return true;
1470
1471 /*
1472 * Navigate to the blank page and insert the given html when
1473 * receiving the next DocumentComplete notification. See the
1474 * MSDN article "Loading HTML content from a Stream".
1475 *
1476 * Note. Stop any pending request. This is required to avoid displaying a
1477 * blank page as a result of consecutive calls to setUrl and/or setText.
1478 * The previous request would otherwise render the new html content and
1479 * reset the html field before the browser actually navigates to the blank
1480 * page as requested below.
1481 *
1482 * Feature in Internet Explorer. Stopping pending requests when no request
1483 * is pending causes a default page 'Action cancelled' to be displayed. The
1484 * workaround is to not invoke 'stop' when no request has been set since
1485 * that instance was created.
1486 */
1487
1488 /*
1489 * Stopping the loading of a page causes DocumentComplete events from previous
1490 * requests to be received before the DocumentComplete for this page. In such
1491 * cases we must be sure to not set the html into the browser too soon, since
1492 * doing so could result in its page being cleared out by a subsequent
1493 * DocumentComplete. The Browser's ReadyState can be used to determine whether
1494 * these extra events will be received or not.
1495 */
1496 if (_getUrl().length() != 0) {
1497 int[] rgdispid = auto.getIDsOfNames(new String[] { "ReadyState" }); //$NON-NLS-1$
1498 Variant pVarResult = auto.getProperty(rgdispid[0]);
1499 if (pVarResult == null) return false;
1500 delaySetText = pVarResult.getInt() != READYSTATE_COMPLETE;
1501 pVarResult.dispose();
1502 rgdispid = auto.getIDsOfNames(new String[] { "Stop" }); //$NON-NLS-1$
1503 auto.invoke(rgdispid[0]);
1504 }
1505
1506 int[] rgdispid = auto.getIDsOfNames(new String[] { "Navigate", "URL" }); //$NON-NLS-1$ //$NON-NLS-2$
1507 Variant[] rgvarg = new Variant[1];
1508 rgvarg[0] = new Variant(ABOUT_BLANK);
1509 int[] rgdispidNamedArgs = new int[1];
1510 rgdispidNamedArgs[0] = rgdispid[1];
1511 boolean oldValue = false;
1512 if (IEVersion >= 7) {
1513 int hResult = OS.CoInternetIsFeatureEnabled(OS.FEATURE_DISABLE_NAVIGATION_SOUNDS, OS.GET_FEATURE_FROM_PROCESS);
1514 oldValue = hResult == COM.S_OK;
1515 OS.CoInternetSetFeatureEnabled(OS.FEATURE_DISABLE_NAVIGATION_SOUNDS, OS.SET_FEATURE_ON_PROCESS, true);
1516 }
1517 Variant pVarResult = auto.invoke(rgdispid[0], rgvarg, rgdispidNamedArgs);
1518 if (IEVersion >= 7) {
1519 OS.CoInternetSetFeatureEnabled(OS.FEATURE_DISABLE_NAVIGATION_SOUNDS, OS.SET_FEATURE_ON_PROCESS, oldValue);
1520 }
1521 rgvarg[0].dispose();
1522 if (pVarResult == null) return false;
1523 boolean result = pVarResult.getType() == OLE.VT_EMPTY;
1524 pVarResult.dispose();
1525 return result;
1526 }
1527
1528 @Override
setUrl(String url, String postData, String headers[])1529 public boolean setUrl(String url, String postData, String headers[]) {
1530 html = uncRedirect = null;
1531
1532 /*
1533 * If the browser has not shown any content yet then first navigate to
1534 * about:blank to work around IE bug http://support.microsoft.com/kb/320153,
1535 * then navigate to the requested url once about:blank has loaded.
1536 */
1537 if (_getUrl().length() == 0 && !ABOUT_BLANK.equalsIgnoreCase(url)) {
1538 pendingText = null;
1539 pendingUrl = new Object[] {url, postData, headers};
1540 performingInitialNavigate = true;
1541 navigate (ABOUT_BLANK, null, null, true);
1542 return true;
1543 }
1544
1545 /*
1546 * Bug in Internet Explorer. For some reason, Navigating to an xml document before
1547 * a previous Navigate has completed will leave the Browser in a bad state if the
1548 * Navigate to the xml document does not complete. This bad state causes a GP when
1549 * the parent window is eventually disposed. The workaround is to issue a Stop before
1550 * navigating to any xml document.
1551 */
1552 if (url.endsWith(".xml")) { //$NON-NLS-1$
1553 int[] rgdispid = auto.getIDsOfNames(new String[] { "Stop" }); //$NON-NLS-1$
1554 auto.invoke(rgdispid[0]);
1555 }
1556 return navigate(url, postData, headers, false);
1557 }
1558
1559 @Override
stop()1560 public void stop() {
1561 /*
1562 * If the browser has not completed its initial navigate to about:blank
1563 * then do not issue Stop here, as this will display the IE error page.
1564 * Just clear the pending url and text fields so that any pending content
1565 * will not be set into the browser when the about:blank navigate completes.
1566 */
1567 if (performingInitialNavigate) {
1568 pendingText = pendingUrl = null;
1569 return;
1570 }
1571
1572 /*
1573 * Feature of IE. Invoking Stop in IE before any content has been shown
1574 * displays a Navigation Cancelled error page. The workaround is to not
1575 * invoke Stop if no content has been shown yet.
1576 */
1577 if (_getUrl().length() == 0) return;
1578
1579 /*
1580 * Ensure that isAboutBlank is set accurately since Stop can be issued at
1581 * any stage in the page load cycle.
1582 */
1583 setAboutBlank(getUrl().startsWith(ABOUT_BLANK));
1584
1585 uncRedirect = null;
1586 int[] rgdispid = auto.getIDsOfNames(new String[] { "Stop" }); //$NON-NLS-1$
1587 auto.invoke(rgdispid[0]);
1588 }
1589
1590 @Override
translateMnemonics()1591 boolean translateMnemonics () {
1592 return false;
1593 }
1594
handleDOMEvent(OleEvent e)1595 void handleDOMEvent (OleEvent e) {
1596 if (e.arguments == null || e.arguments.length == 0) return; /* for IE5 */
1597
1598 Variant arg = e.arguments[0];
1599 OleAutomation event = arg.getAutomation();
1600 int[] rgdispid = event.getIDsOfNames(new String[]{ PROPERTY_TYPE });
1601 int dispIdMember = rgdispid[0];
1602 Variant pVarResult = event.getProperty(dispIdMember);
1603 String eventType = pVarResult.getString();
1604 pVarResult.dispose();
1605
1606 if (eventType.equals(EVENT_KEYDOWN)) {
1607 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_KEYCODE });
1608 dispIdMember = rgdispid[0];
1609 pVarResult = event.getProperty(dispIdMember);
1610 lastKeyCode = translateKey (pVarResult.getInt());
1611 pVarResult.dispose();
1612
1613 rgdispid = event.getIDsOfNames (new String[] {PROPERTY_RETURNVALUE});
1614 pVarResult = event.getProperty (rgdispid[0]);
1615 boolean consume = pVarResult != null && pVarResult.getType () == OLE.VT_BOOL && !pVarResult.getBoolean ();
1616 pVarResult.dispose ();
1617
1618 MSG msg = new MSG ();
1619 int flags = OS.PM_NOYIELD | (consume ? OS.PM_REMOVE : OS.PM_NOREMOVE);
1620 if (OS.PeekMessage (msg, frame.handle, OS.WM_CHAR, OS.WM_CHAR, flags)) {
1621 /* a keypress will be received for this key so don't send KeyDown here */
1622 event.dispose();
1623 return;
1624 }
1625
1626 if (consume) {
1627 /*
1628 * an event should not be sent if another listener has vetoed the
1629 * KeyDown (this is for non-character cases like arrow keys, etc.)
1630 */
1631 event.dispose();
1632 return;
1633 }
1634
1635 /* if this is a repeating key then an event should not be fired for it */
1636 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_REPEAT });
1637 dispIdMember = rgdispid[0];
1638 pVarResult = event.getProperty(dispIdMember);
1639 boolean repeating = pVarResult.getBoolean();
1640 pVarResult.dispose();
1641 if (repeating) {
1642 event.dispose();
1643 return;
1644 }
1645
1646 int mask = 0;
1647 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_ALTKEY });
1648 dispIdMember = rgdispid[0];
1649 pVarResult = event.getProperty(dispIdMember);
1650 if (pVarResult.getBoolean()) mask |= SWT.ALT;
1651 pVarResult.dispose();
1652
1653 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_CTRLKEY });
1654 dispIdMember = rgdispid[0];
1655 pVarResult = event.getProperty(dispIdMember);
1656 if (pVarResult.getBoolean()) mask |= SWT.CTRL;
1657 pVarResult.dispose();
1658
1659 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_SHIFTKEY });
1660 dispIdMember = rgdispid[0];
1661 pVarResult = event.getProperty(dispIdMember);
1662 if (pVarResult.getBoolean()) mask |= SWT.SHIFT;
1663 pVarResult.dispose();
1664
1665 Event keyEvent = new Event ();
1666 keyEvent.widget = browser;
1667 keyEvent.type = SWT.KeyDown;
1668 keyEvent.keyCode = lastKeyCode;
1669 keyEvent.stateMask = mask;
1670 keyEvent.stateMask &= ~lastKeyCode; /* remove current keydown if it's a state key */
1671 /*
1672 * keypress events are not received for Backspace, Enter, Delete and
1673 * Tab, so KeyDown events are sent for them here. Set the KeyDown
1674 * event's character field and IE's lastCharCode field for these keys
1675 * so that the Browser's key events are consistent with other controls.
1676 */
1677 switch (lastKeyCode) {
1678 case SWT.BS: lastCharCode = keyEvent.character = SWT.BS; break;
1679 case SWT.CR: lastCharCode = keyEvent.character = SWT.CR; break;
1680 case SWT.DEL: lastCharCode = keyEvent.character = SWT.DEL; break;
1681 case SWT.TAB: lastCharCode = keyEvent.character = SWT.TAB; break;
1682 }
1683
1684 if (!sendKeyEvent(keyEvent)) {
1685 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_RETURNVALUE });
1686 dispIdMember = rgdispid[0];
1687 Variant pVarFalse = new Variant(false);
1688 event.setProperty(dispIdMember, pVarFalse);
1689 pVarFalse.dispose();
1690 }
1691
1692 /*
1693 * Pressing F5 refreshes the current page. If this is about to happen
1694 * then set isRefresh to true so that received IE events will be treated
1695 * accordingly.
1696 */
1697 if (lastKeyCode == SWT.F5) isRefresh = true;
1698
1699 event.dispose();
1700 return;
1701 }
1702
1703 if (eventType.equals(EVENT_KEYPRESS)) {
1704 int mask = 0;
1705 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_CTRLKEY });
1706 dispIdMember = rgdispid[0];
1707 pVarResult = event.getProperty(dispIdMember);
1708 if (pVarResult.getBoolean()) mask |= SWT.CTRL;
1709 pVarResult.dispose();
1710
1711 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_SHIFTKEY });
1712 dispIdMember = rgdispid[0];
1713 pVarResult = event.getProperty(dispIdMember);
1714 if (pVarResult.getBoolean()) mask |= SWT.SHIFT;
1715 pVarResult.dispose();
1716
1717 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_ALTKEY });
1718 dispIdMember = rgdispid[0];
1719 pVarResult = event.getProperty(dispIdMember);
1720 if (pVarResult.getBoolean()) mask |= SWT.ALT;
1721 pVarResult.dispose();
1722
1723 /* in the keypress event the keyCode actually corresponds to the character code */
1724 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_KEYCODE });
1725 dispIdMember = rgdispid[0];
1726 pVarResult = event.getProperty(dispIdMember);
1727 lastCharCode = pVarResult.getInt();
1728 pVarResult.dispose();
1729
1730 /*
1731 * WebSite.TranslateAccelerator() explicitly does not translate OS.VK_RETURN
1732 * keys, so the PeekMessage check in the keydown handler always allows a
1733 * KeyDown to be sent for this key. However, keydown and keypress events are
1734 * both sometimes received for OS.VK_RETURN, depending on the page's focus
1735 * control. To handle this, do not send a KeyDown for CR or LF here since
1736 * one is always sent for it from the keydown handler.
1737 */
1738 if (lastCharCode == SWT.CR || lastCharCode == SWT.LF) {
1739 event.dispose();
1740 return;
1741 }
1742
1743 Event keyEvent = new Event ();
1744 keyEvent.widget = browser;
1745 keyEvent.type = SWT.KeyDown;
1746 keyEvent.keyCode = lastKeyCode;
1747 keyEvent.character = (char)lastCharCode;
1748 keyEvent.stateMask = mask;
1749 if (!sendKeyEvent(keyEvent)) {
1750 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_RETURNVALUE });
1751 dispIdMember = rgdispid[0];
1752 Variant pVarFalse = new Variant(false);
1753 event.setProperty(dispIdMember, pVarFalse);
1754 pVarFalse.dispose();
1755 }
1756
1757 event.dispose();
1758 return;
1759 }
1760
1761 if (eventType.equals(EVENT_KEYUP)) {
1762 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_KEYCODE });
1763 dispIdMember = rgdispid[0];
1764 pVarResult = event.getProperty(dispIdMember);
1765 int keyCode = translateKey (pVarResult.getInt());
1766 pVarResult.dispose();
1767
1768 /*
1769 * if a key code could not be determined for this key then it's a
1770 * key for which key events are not sent (eg.- the Windows key)
1771 */
1772 if (keyCode == 0) {
1773 lastKeyCode = lastCharCode = 0;
1774 event.dispose();
1775 return;
1776 }
1777
1778 if (keyCode != lastKeyCode) {
1779 /* keyup does not correspond to the last keydown */
1780 lastKeyCode = keyCode;
1781 lastCharCode = 0;
1782 }
1783
1784 int mask = 0;
1785 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_CTRLKEY });
1786 dispIdMember = rgdispid[0];
1787 pVarResult = event.getProperty(dispIdMember);
1788 if (pVarResult.getBoolean()) mask |= SWT.CTRL;
1789 pVarResult.dispose();
1790
1791 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_ALTKEY });
1792 dispIdMember = rgdispid[0];
1793 pVarResult = event.getProperty(dispIdMember);
1794 if (pVarResult.getBoolean()) mask |= SWT.ALT;
1795 pVarResult.dispose();
1796
1797 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_SHIFTKEY });
1798 dispIdMember = rgdispid[0];
1799 pVarResult = event.getProperty(dispIdMember);
1800 if (pVarResult.getBoolean()) mask |= SWT.SHIFT;
1801 pVarResult.dispose();
1802
1803 Event keyEvent = new Event ();
1804 keyEvent.widget = browser;
1805 keyEvent.type = SWT.KeyUp;
1806 keyEvent.keyCode = lastKeyCode;
1807 keyEvent.character = (char)lastCharCode;
1808 keyEvent.stateMask = mask;
1809 switch (lastKeyCode) {
1810 case SWT.SHIFT:
1811 case SWT.CONTROL:
1812 case SWT.ALT:
1813 case SWT.COMMAND: {
1814 keyEvent.stateMask |= lastKeyCode;
1815 }
1816 }
1817 browser.notifyListeners (keyEvent.type, keyEvent);
1818 if (!keyEvent.doit) {
1819 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_RETURNVALUE });
1820 dispIdMember = rgdispid[0];
1821 Variant pVarFalse = new Variant(false);
1822 event.setProperty(dispIdMember, pVarFalse);
1823 pVarFalse.dispose();
1824 }
1825
1826 lastKeyCode = lastCharCode = 0;
1827 event.dispose();
1828 return;
1829 }
1830
1831 /*
1832 * Feature in IE. MouseOver/MouseOut events are fired any time the mouse enters
1833 * or exits any element within the Browser. To ensure that SWT events are only
1834 * fired for mouse movements into or out of the Browser, do not fire an event if
1835 * the element being exited (on MouseOver) or entered (on MouseExit) is within
1836 * the Browser.
1837 */
1838 if (eventType.equals(EVENT_MOUSEOVER)) {
1839 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_FROMELEMENT });
1840 dispIdMember = rgdispid[0];
1841 pVarResult = event.getProperty(dispIdMember);
1842 boolean isInternal = pVarResult.getType() != COM.VT_EMPTY;
1843 pVarResult.dispose();
1844 if (isInternal) {
1845 event.dispose();
1846 return;
1847 }
1848 }
1849 if (eventType.equals(EVENT_MOUSEOUT)) {
1850 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_TOELEMENT });
1851 dispIdMember = rgdispid[0];
1852 pVarResult = event.getProperty(dispIdMember);
1853 boolean isInternal = pVarResult.getType() != COM.VT_EMPTY;
1854 pVarResult.dispose();
1855 if (isInternal) {
1856 event.dispose();
1857 return;
1858 }
1859 }
1860
1861 int mask = 0;
1862 Event newEvent = new Event();
1863 newEvent.widget = browser;
1864
1865 /*
1866 * The position of mouse events is received in screen-relative coordinates
1867 * in order to handle pages with frames, since frames express their event
1868 * coordinates relative to themselves rather than relative to their top-
1869 * level page. Convert screen-relative coordinates to be browser-relative.
1870 */
1871 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_SCREENX });
1872 dispIdMember = rgdispid[0];
1873 pVarResult = event.getProperty(dispIdMember);
1874 int screenX = pVarResult.getInt();
1875 pVarResult.dispose();
1876
1877 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_SCREENY });
1878 dispIdMember = rgdispid[0];
1879 pVarResult = event.getProperty(dispIdMember);
1880 int screenY = pVarResult.getInt();
1881 pVarResult.dispose();
1882
1883 Point position = DPIUtil.autoScaleDown(new Point(screenX, screenY)); // To Points
1884 position = browser.getDisplay().map(null, browser, position);
1885 newEvent.x = position.x; newEvent.y = position.y;
1886
1887 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_CTRLKEY });
1888 dispIdMember = rgdispid[0];
1889 pVarResult = event.getProperty(dispIdMember);
1890 if (pVarResult.getBoolean()) mask |= SWT.CTRL;
1891 pVarResult.dispose();
1892
1893 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_ALTKEY });
1894 dispIdMember = rgdispid[0];
1895 pVarResult = event.getProperty(dispIdMember);
1896 if (pVarResult.getBoolean()) mask |= SWT.ALT;
1897 pVarResult.dispose();
1898
1899 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_SHIFTKEY });
1900 dispIdMember = rgdispid[0];
1901 pVarResult = event.getProperty(dispIdMember);
1902 if (pVarResult.getBoolean()) mask |= SWT.SHIFT;
1903 pVarResult.dispose();
1904
1905 newEvent.stateMask = mask;
1906
1907 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_BUTTON });
1908 dispIdMember = rgdispid[0];
1909 pVarResult = event.getProperty(dispIdMember);
1910 int button = pVarResult.getInt();
1911 pVarResult.dispose();
1912 switch (button) {
1913 case 1: button = 1; break;
1914 case 2: button = 3; break;
1915 case 4: button = 2; break;
1916 }
1917
1918 if (eventType.equals(EVENT_MOUSEDOWN)) {
1919 newEvent.type = SWT.MouseDown;
1920 newEvent.button = button;
1921 newEvent.count = 1;
1922 } else if (eventType.equals(EVENT_MOUSEUP) || eventType.equals(EVENT_DRAGEND)) {
1923 newEvent.type = SWT.MouseUp;
1924 newEvent.button = button != 0 ? button : 1; /* button assumed to be 1 for dragends */
1925 newEvent.count = 1;
1926 switch (newEvent.button) {
1927 case 1: newEvent.stateMask |= SWT.BUTTON1; break;
1928 case 2: newEvent.stateMask |= SWT.BUTTON2; break;
1929 case 3: newEvent.stateMask |= SWT.BUTTON3; break;
1930 case 4: newEvent.stateMask |= SWT.BUTTON4; break;
1931 case 5: newEvent.stateMask |= SWT.BUTTON5; break;
1932 }
1933 } else if (eventType.equals(EVENT_MOUSEWHEEL)) {
1934 newEvent.type = SWT.MouseWheel;
1935 rgdispid = event.getIDsOfNames(new String[] { PROPERTY_WHEELDELTA });
1936 dispIdMember = rgdispid[0];
1937 pVarResult = event.getProperty(dispIdMember);
1938 newEvent.count = pVarResult.getInt () / 120 * 3;
1939 pVarResult.dispose();
1940 } else if (eventType.equals(EVENT_MOUSEMOVE)) {
1941 /*
1942 * Feature in IE. Spurious and redundant mousemove events are often received. The workaround
1943 * is to not fire MouseMove events whose x and y values match the last MouseMove.
1944 */
1945 if (newEvent.x == lastMouseMoveX && newEvent.y == lastMouseMoveY) {
1946 event.dispose();
1947 return;
1948 }
1949 newEvent.type = SWT.MouseMove;
1950 lastMouseMoveX = newEvent.x; lastMouseMoveY = newEvent.y;
1951 } else if (eventType.equals(EVENT_MOUSEOVER)) {
1952 newEvent.type = SWT.MouseEnter;
1953 } else if (eventType.equals(EVENT_MOUSEOUT)) {
1954 newEvent.type = SWT.MouseExit;
1955 } else if (eventType.equals(EVENT_DRAGSTART)) {
1956 newEvent.type = SWT.DragDetect;
1957 newEvent.button = 1; /* button assumed to be 1 for dragstarts */
1958 newEvent.stateMask |= SWT.BUTTON1;
1959 }
1960
1961 event.dispose();
1962 browser.notifyListeners(newEvent.type, newEvent);
1963
1964 if (eventType.equals(EVENT_DOUBLECLICK)) {
1965 newEvent = new Event ();
1966 newEvent.widget = browser;
1967 newEvent.type = SWT.MouseDoubleClick;
1968 newEvent.x = position.x; newEvent.y = position.y;
1969 newEvent.stateMask = mask;
1970 newEvent.type = SWT.MouseDoubleClick;
1971 newEvent.button = 1; /* dblclick only comes for button 1 and does not set the button property */
1972 newEvent.count = 2;
1973 browser.notifyListeners (newEvent.type, newEvent);
1974 }
1975 }
1976
hookDOMListeners(OleAutomation webBrowser, final boolean isTop)1977 void hookDOMListeners(OleAutomation webBrowser, final boolean isTop) {
1978 int[] rgdispid = webBrowser.getIDsOfNames(new String[] { PROPERTY_DOCUMENT });
1979 int dispIdMember = rgdispid[0];
1980 Variant pVarResult = webBrowser.getProperty(dispIdMember);
1981 if (pVarResult == null) return;
1982 if (pVarResult.getType() == COM.VT_EMPTY) {
1983 pVarResult.dispose();
1984 return;
1985 }
1986 final OleAutomation document = pVarResult.getAutomation();
1987 pVarResult.dispose();
1988
1989 /*
1990 * In some cases, such as setting the Browser's content from a string,
1991 * NavigateComplete2 is received multiple times for a top-level document.
1992 * For cases like this, any previously-hooked DOM listeners must be
1993 * removed from the document before hooking the new set of listeners,
1994 * otherwise multiple sets of events will be received.
1995 */
1996 unhookDOMListeners (new OleAutomation[] {document});
1997
1998 site.addEventListener(document, COM.IIDIHTMLDocumentEvents2, COM.DISPID_HTMLDOCUMENTEVENTS_ONKEYDOWN, domListener);
1999 site.addEventListener(document, COM.IIDIHTMLDocumentEvents2, COM.DISPID_HTMLDOCUMENTEVENTS_ONKEYPRESS, domListener);
2000 site.addEventListener(document, COM.IIDIHTMLDocumentEvents2, COM.DISPID_HTMLDOCUMENTEVENTS_ONKEYUP, domListener);
2001 site.addEventListener(document, COM.IIDIHTMLDocumentEvents2, COM.DISPID_HTMLDOCUMENTEVENTS_ONMOUSEDOWN, domListener);
2002 site.addEventListener(document, COM.IIDIHTMLDocumentEvents2, COM.DISPID_HTMLDOCUMENTEVENTS_ONMOUSEUP, domListener);
2003 site.addEventListener(document, COM.IIDIHTMLDocumentEvents2, COM.DISPID_HTMLDOCUMENTEVENTS_ONMOUSEWHEEL, domListener);
2004 site.addEventListener(document, COM.IIDIHTMLDocumentEvents2, COM.DISPID_HTMLDOCUMENTEVENTS_ONDBLCLICK, domListener);
2005 site.addEventListener(document, COM.IIDIHTMLDocumentEvents2, COM.DISPID_HTMLDOCUMENTEVENTS_ONMOUSEMOVE, domListener);
2006 site.addEventListener(document, COM.IIDIHTMLDocumentEvents2, COM.DISPID_HTMLDOCUMENTEVENTS_ONDRAGSTART, domListener);
2007 site.addEventListener(document, COM.IIDIHTMLDocumentEvents2, COM.DISPID_HTMLDOCUMENTEVENTS_ONDRAGEND, domListener);
2008 /* ensure that enter/exit are only fired once, by the top-level document */
2009 if (isTop) {
2010 site.addEventListener(document, COM.IIDIHTMLDocumentEvents2, COM.DISPID_HTMLDOCUMENTEVENTS_ONMOUSEOVER, domListener);
2011 site.addEventListener(document, COM.IIDIHTMLDocumentEvents2, COM.DISPID_HTMLDOCUMENTEVENTS_ONMOUSEOUT, domListener);
2012 }
2013
2014 OleAutomation[] newDocuments = new OleAutomation[documents.length + 1];
2015 System.arraycopy(documents, 0, newDocuments, 0, documents.length);
2016 newDocuments[documents.length] = document;
2017 documents = newDocuments;
2018 }
2019
unhookDOMListeners(OleAutomation[] documents)2020 void unhookDOMListeners(OleAutomation[] documents) {
2021 char[] buffer = (COM.IIDIHTMLDocumentEvents2 + '\0').toCharArray();
2022 GUID guid = new GUID();
2023 if (COM.IIDFromString(buffer, guid) == COM.S_OK) {
2024 for (OleAutomation document : documents) {
2025 site.removeEventListener(document, guid, COM.DISPID_HTMLDOCUMENTEVENTS_ONKEYDOWN, domListener);
2026 site.removeEventListener(document, guid, COM.DISPID_HTMLDOCUMENTEVENTS_ONKEYPRESS, domListener);
2027 site.removeEventListener(document, guid, COM.DISPID_HTMLDOCUMENTEVENTS_ONKEYUP, domListener);
2028 site.removeEventListener(document, guid, COM.DISPID_HTMLDOCUMENTEVENTS_ONMOUSEDOWN, domListener);
2029 site.removeEventListener(document, guid, COM.DISPID_HTMLDOCUMENTEVENTS_ONMOUSEUP, domListener);
2030 site.removeEventListener(document, guid, COM.DISPID_HTMLDOCUMENTEVENTS_ONMOUSEWHEEL, domListener);
2031 site.removeEventListener(document, guid, COM.DISPID_HTMLDOCUMENTEVENTS_ONDBLCLICK, domListener);
2032 site.removeEventListener(document, guid, COM.DISPID_HTMLDOCUMENTEVENTS_ONMOUSEMOVE, domListener);
2033 site.removeEventListener(document, guid, COM.DISPID_HTMLDOCUMENTEVENTS_ONDRAGSTART, domListener);
2034 site.removeEventListener(document, guid, COM.DISPID_HTMLDOCUMENTEVENTS_ONDRAGEND, domListener);
2035 site.removeEventListener(document, guid, COM.DISPID_HTMLDOCUMENTEVENTS_ONMOUSEOVER, domListener);
2036 site.removeEventListener(document, guid, COM.DISPID_HTMLDOCUMENTEVENTS_ONMOUSEOUT, domListener);
2037 }
2038 }
2039 }
2040 }
2041